Aller au contenu principal

Merge, rebase, reset, revert, cherry-pick, reflog : quelle commande Git pour quelle situation

Annuler un commit déjà poussé, récupérer une branche supprimée, copier un correctif d'une branche à l'autre, sortir d'un conflit sans tout casser : chaque situation a sa commande, et les confondre coûte cher. Cet article part de ce que Git garde en mémoire, puis passe les sept commandes en revue avec des cas réels, un arbre de décision et les pièges qui détruisent du travail.

Outils & environnement ·
Adel LATIBI
Adel LATIBI

Le Briefing Dev - les ressources et actus de la semaine, droit dans ta boîte chaque vendredi gratuitement.

En vous inscrivant, vous acceptez de recevoir notre newsletter. Désinscription possible à tout moment.

Git est installé sur à peu près toutes les machines de développeurs du monde. Dans la plupart des équipes, pourtant, quatre commandes font tout le travail quotidien : add, commit, push, pull. Le reste de l'outil reste une zone grise dans laquelle on n'entre qu'en cas d'urgence, souvent avec une réponse de forum copiée à la hâte.

Le jour où un commit part sur la mauvaise branche, où un merge produit un conflit de quarante lignes, ou quand une branche disparaît après une commande mal tapée, cette zone grise devient le seul endroit où chercher. Les personnes en reconversion et les développeurs juniors vivent souvent ce moment comme une catastrophe, alors que Git a presque toujours gardé une copie de ce qui semble perdu.

Ce qui manque rarement, c'est l'information sur chaque commande prise isolément. Ce qui manque presque toujours, c'est la carte qui dit laquelle choisir. Cet article donne cette carte : un principe à comprendre une fois, puis merge, rebase, la gestion des conflits, reset, revert, cherry-pick et reflog, chacun rattaché à la situation qu'il résout.

Le problème : sept commandes qui se ressemblent

Prenons une situation banale. Un correctif urgent a été commité sur une branche de fonctionnalité au lieu de main, puis poussé. Une recherche rapide propose au moins quatre réponses : faire un reset, faire un revert, faire un cherry-pick, ou faire un rebase. Toutes fonctionnent dans un contexte donné. Deux d'entre elles, appliquées ici, réécrivent un historique que d'autres personnes ont peut-être déjà récupéré.

La confusion vient de ce que ces commandes touchent toutes à l'historique, mais pas de la même manière. Certaines ajoutent des commits, d'autres en recréent, d'autres déplacent simplement un pointeur. Sans savoir laquelle fait quoi, on choisit au hasard, et le hasard tombe parfois sur git reset --hard suivi d'un git push --force qui efface le travail d'un collègue sur le dépôt partagé.

Ce que les tutoriels sautent souvent, c'est la question qui précède le choix : ce commit a-t-il déjà quitté ta machine ? La réponse élimine la moitié des options avant même d'ouvrir la documentation.

Le principe : des commits immuables et des étiquettes qui bougent

Un commit Git est un instantané du projet, identifié par une empreinte calculée à partir de son contenu et de son parent. Si le contenu change, l'empreinte change : un commit ne se modifie jamais, il se remplace par un nouveau. Une branche, elle, n'est qu'une étiquette qui pointe vers un commit, et HEAD désigne l'endroit où tu te trouves en ce moment.

Deux conséquences découlent de ce modèle. D'abord, « réécrire l'historique » veut dire créer de nouveaux commits et déplacer l'étiquette vers eux : les anciens commits existent toujours, simplement plus aucune branche ne pointe dessus. Ensuite, réécrire un historique qui n'existe que sur ta machine ne gêne personne, alors que réécrire un historique déjà poussé oblige tous ceux qui l'ont récupéré à réconcilier leur copie avec la tienne.

Quelle est la situation ? Commit à annuler, déjà poussé Commit à annuler, resté en local Commit ou branche introuvable Un commit précis à copier ailleurs git revert git reset git reflog git cherry-pick Ce qui est partagé se corrige par un nouveau commit. Ce qui est local peut se réécrire.
Choisir une commande de correction selon que le commit a quitté ta machine ou non.

Merge et rebase n'apparaissent pas dans ce schéma parce qu'ils répondent à une autre question, celle de l'intégration de deux lignes de travail. Les conflits, eux, peuvent surgir avec l'une ou l'autre. Si les notions de commit, de branche et de push sont encore fraîches, le guide Git pour débutants reprend ces bases avant d'aller plus loin.

Les commandes, une situation à la fois

Merge : réunir deux branches en gardant leur histoire

Une branche de fonctionnalité est terminée et doit rejoindre main. Le merge relie les deux historiques. Si main n'a pas bougé depuis la création de la branche, Git avance simplement l'étiquette, c'est le fast-forward. Sinon, il crée un commit de fusion qui a deux parents.

git switch main
git pull
git merge --no-ff feature/paiement
git push

L'option --no-ff force la création du commit de fusion même quand un fast-forward serait possible, ce qui garde une trace visible du moment où la fonctionnalité est entrée. Sur un dépôt d'équipe, cette fusion passe de préférence par une pull request sur GitHub plutôt que par un merge local poussé directement, pour des raisons détaillées dans l'article sur les stratégies de branches Git sur GitHub.

Rebase : rejouer ses commits sur une base à jour

Ta branche a été créée il y a une semaine, et main a reçu douze commits depuis. Le rebase prend tes commits, les met de côté, avance ta branche au bout de main, puis rejoue tes commits un par un. Le résultat est un historique linéaire, sans commit de fusion, comme si tu avais commencé ton travail ce matin.

git switch feature/paiement
git fetch origin
git rebase origin/main
git push --force-with-lease

Les commits rejoués reçoivent de nouvelles empreintes, d'où le push forcé à la fin. L'option --force-with-lease refuse d'écraser la branche distante si quelqu'un y a poussé entre-temps, ce que --force seul ne vérifie pas. La règle qui en découle tient en une phrase : on rebase sa propre branche, jamais une branche sur laquelle d'autres personnes travaillent.

Le rebase interactif sert à autre chose : nettoyer ses commits avant de demander une relecture. Les trois « wip » et le « fix typo » fusionnent en un commit lisible.

git commit --fixup a1b2c3d
git rebase -i --autosquash origin/main

Avec --fixup, Git marque le commit comme correction d'un commit précédent, et --autosquash le range automatiquement à la bonne place dans la liste du rebase interactif. Mon avis : c'est la combinaison la plus sous-utilisée de Git, et celle qui change le plus la qualité d'une relecture de code.

Gérer un conflit sans paniquer

Un conflit apparaît quand deux branches ont modifié les mêmes lignes et que Git ne peut pas décider à ta place. Il s'arrête, marque les fichiers concernés et attend. Rien n'est perdu à ce stade, et deux commandes permettent toujours de revenir en arrière : git merge --abort ou git rebase --abort.

Un réglage rend les conflits nettement plus lisibles, en affichant aussi la version d'origine commune aux deux branches :

git config --global merge.conflictStyle zdiff3
<<<<<<< HEAD
const TVA = 0.20;
||||||| daa0084
const TVA = 0.196;
=======
const TAUX_TVA = 0.196;
>>>>>>> feature/renommage

La section du milieu, introduite par l'empreinte du commit commun, montre l'état de départ. On lit alors que la branche courante a changé la valeur et que l'autre a renommé la constante : la bonne résolution garde les deux changements, const TAUX_TVA = 0.20;, alors que sans la base commune on aurait facilement choisi un côté au hasard. Une fois les marqueurs supprimés, git add sur le fichier, puis git merge --continue ou git rebase --continue. Le style zdiff3 existe depuis Git 2.35, publié en 2022.

Pour les conflits qui reviennent à chaque rebase d'une branche longue, git config --global rerere.enabled true demande à Git de mémoriser tes résolutions et de les réappliquer seul la fois suivante.

Reset : déplacer l'étiquette en arrière

Le dernier commit, encore local, contient une erreur ou un fichier qui n'avait rien à faire là. Reset déplace la branche vers un commit antérieur. Ses trois modes diffèrent par ce qu'ils font des modifications annulées.

# annule le commit, garde les changements prêts à recommiter
git reset --soft HEAD~1
 
# annule le commit, garde les changements dans les fichiers (mode par défaut)
git reset HEAD~1
 
# annule le commit ET efface les changements
git reset --hard HEAD~1

Le mode --soft sert à refaire un commit mal découpé ou mal nommé, le mode par défaut à reprendre le travail avant de recommiter autrement. Le mode --hard est le seul qui détruit quelque chose : les modifications jamais commitées qu'il efface ne figurent nulle part dans l'historique. Avant de le lancer sur un répertoire qui contient du travail en cours, un git stash met ce travail à l'abri.

Revert : annuler sans réécrire

Le commit fautif est déjà sur main, poussé, peut-être déployé. Revert crée un nouveau commit qui applique l'inverse exact du commit visé. L'historique garde la trace de l'erreur et de sa correction, et personne n'a besoin de forcer quoi que ce soit.

git revert 4f9e2ab
git push

Annuler un commit de fusion demande de préciser quel parent conserver, en général le premier, celui de la branche principale :

git revert -m 1 8c71d0e

Ce cas cache un piège décrit plus bas dans la section dédiée.

Cherry-pick : copier un commit précis

Revenons au correctif urgent commité sur la mauvaise branche. Il doit partir en production maintenant, alors que le reste de la branche n'est pas prêt. Cherry-pick copie ce seul commit sur la branche courante, avec une nouvelle empreinte.

git switch -c hotfix/arrondi-tva origin/main
git cherry-pick -x 3d5a7f1
git push -u origin hotfix/arrondi-tva

L'option -x ajoute au message la mention de l'empreinte d'origine, ce qui permet de retrouver plus tard d'où vient la copie. C'est aussi la commande des branches de maintenance : un correctif fait sur main est reporté sur release/2.x. Une plage de commits se copie avec git cherry-pick A^..B, mais au-delà de trois ou quatre commits, un rebase ou un merge raconte généralement mieux ce qui s'est passé.

Reflog : retrouver ce qui semble perdu

Une branche supprimée trop vite, un rebase qui a mal tourné, un reset --hard sur le mauvais commit. Le reflog est le journal local de tous les endroits où HEAD a pointé, y compris ceux qu'aucune branche ne référence plus.

git reflog
# 7e1c9b2 HEAD@{0}: reset: moving to HEAD~3
# a84f310 HEAD@{1}: commit: Ajoute le calcul des frais de port
# 2b6d4e8 HEAD@{2}: commit: Valide le formulaire d'adresse
 
git branch recuperation a84f310

La branche recuperation pointe de nouveau sur le travail effacé, qu'on peut ensuite fusionner ou inspecter tranquillement. Juste après un merge, un rebase ou un reset raté, Git garde aussi l'ancienne position dans ORIG_HEAD, et git reset --hard ORIG_HEAD remet tout comme avant. Par défaut, les entrées du reflog sont conservées 90 jours, et 30 jours pour les commits qui ne sont plus accessibles depuis aucune branche. Le reflog n'est jamais poussé : il n'existe que dans ton dépôt local, ce qui le rend inutile sur une copie fraîchement clonée.

Les pièges qui détruisent du travail

Forcer le push sur une branche partagée

Un git push --force sur main après un reset ou un rebase remplace l'historique distant par le tien. Les commits que les autres ont poussés entre-temps disparaissent du dépôt partagé. La meilleure protection se règle côté GitHub, en interdisant purement et simplement le push forcé sur la branche principale.

Inverser « ours » et « theirs » pendant un rebase

Pendant un merge, --ours désigne ta branche courante. Pendant un rebase, c'est l'inverse : Git rejoue tes commits sur la branche cible, donc « ours » désigne la branche cible et « theirs » ton propre travail. Résoudre un conflit avec git restore --ours en pensant garder sa version efface précisément sa version.

Refusionner une branche après le revert de son merge

Une fonctionnalité fusionnée puis annulée avec git revert -m 1 est corrigée sur sa branche, puis refusionnée. Seules les nouvelles corrections apparaissent : pour Git, les commits d'origine sont déjà intégrés, et le revert les a neutralisés. La solution consiste à annuler le revert lui-même avant de refusionner, un comportement documenté par le projet Git mais rarement expliqué.

Croire que le reflog sauve tout

Le reflog ne connaît que les commits. Un fichier modifié puis effacé par reset --hard sans jamais avoir été commité est perdu. S'il avait été ajouté avec git add, son contenu traîne encore dans la base d'objets et peut se retrouver avec git fsck --lost-found, au prix d'une fouille pénible parmi des fichiers sans nom. Commiter souvent sur une branche locale, quitte à nettoyer avec un rebase interactif ensuite, reste la meilleure assurance.

Copier une commande sans lire ce qu'elle fait

Les assistants de code proposent volontiers un reset --hard suivi d'un push forcé pour « nettoyer » une situation confuse. La commande fonctionne, et elle peut effacer le travail d'une autre personne. Le réflexe utile consiste à lire l'état réel du dépôt avec git status et git log --oneline --graph --all avant de toucher quoi que ce soit, une démarche proche de celle décrite dans l'article apprendre à débugger. Le sujet plus large du code généré qu'on n'a pas relu est traité dans vibe coding sans perdre le contrôle.

Pratiquer avant d'en avoir besoin

Toutes ces commandes s'apprennent mal en situation d'urgence. Un dépôt jetable, créé exprès, permet de provoquer un conflit, de faire un reset désastreux et de le réparer avec le reflog en vingt minutes, sans enjeu. Refaire l'exercice deux ou trois fois suffit en général pour que la séquence devienne un réflexe.

Ces commandes reviennent aussi en entretien technique, souvent sous la forme d'une question de mise en situation plutôt que d'une définition, comme le montre l'article sur l'entretien technique junior. Un historique propre sur les projets publiés compte également : un recruteur qui ouvre un dépôt voit les messages de commit avant de voir le code, un point développé dans le premier projet d'un CV de développeur junior.

Pour un apprentissage encadré, la formation Git et GitHub : gestion de versions pour débutants couvre branches, fusions, conflits et travail collaboratif sur GitHub. La suite logique est l'automatisation de ce qui se passe à chaque push, avec la formation CI/CD avec GitHub Actions. Pour les équipes qui doivent en plus tracer et sécuriser leur chaîne de livraison, Git, GitHub Actions et Cyber Resilience Act reprend la stratégie de branches, les pull requests et la résolution de conflits dans un contexte .NET industriel.

La première chose à faire est peut-être la plus simple : taper git reflog dans un projet existant et lire ce qui s'y trouve.

Questions fréquentes

Faut-il préférer merge ou rebase ?

Les deux ont leur place. Le rebase sert à mettre à jour sa propre branche et à nettoyer ses commits avant relecture, tant qu'elle n'est pas partagée. Le merge sert à intégrer une branche terminée dans une branche commune, sans réécrire ce que d'autres ont déjà récupéré. Beaucoup d'équipes combinent les deux : rebase local, puis fusion via pull request.

Comment annuler un git push ?

Sur une branche partagée, avec git revert suivi d'un push normal : un nouveau commit annule l'ancien sans réécrire l'historique distant. Sur une branche personnelle que personne d'autre n'a récupérée, un git reset suivi d'un git push --force-with-lease est acceptable.

Peut-on récupérer une branche supprimée ?

Oui, en général. Le message affiché par git branch -D indique l'empreinte du dernier commit de la branche, et git reflog la retrouve aussi. Il suffit ensuite de recréer une branche sur cette empreinte avec git branch nom-de-branche empreinte. Si la branche existait sur GitHub, l'interface propose aussi de la restaurer depuis la pull request fermée.

Quelle différence entre git reset et git revert ?

Reset déplace la branche vers un commit antérieur, ce qui retire des commits de l'historique visible et suppose de forcer le push s'ils étaient déjà partagés. Revert ajoute un commit qui applique l'inverse d'un commit existant, sans rien retirer. Le premier convient au travail local, le second à tout ce qui a déjà été poussé.

Un cherry-pick crée-t-il un doublon dans l'historique ?

Oui, la copie a une empreinte différente de l'original. Quand les deux branches sont fusionnées plus tard, Git reconnaît le plus souvent que le changement est identique et ne produit pas de conflit, mais l'historique contient bien deux commits. L'option -x aide à faire le lien entre les deux en notant l'empreinte d'origine dans le message.

Pourquoi Git signale-t-il des conflits sur des lignes que personne n'a touchées ?

Le plus souvent à cause des fins de ligne ou de l'indentation : un éditeur sous Windows qui convertit les fins de ligne, ou un formateur de code qui réindente le fichier entier. Git voit alors chaque ligne comme modifiée. Un fichier .gitattributes qui fixe les fins de ligne et un formateur partagé par toute l'équipe règlent le problème à la source.

Vous êtes expert ?

Partagez votre expertise sur notre blog

Tutoriel, retour d'expérience, analyse - publiez un article invité et gagnez en visibilité.

Écrire pour nous