Git revert vs reset : lequel sauve ton historique ?
Git revert vs reset : quelle commande annule des commits sans risque, quand reset --hard détruit du travail, et comment chacun réécrit l'historique partagé.
TL;DR : git revert ajoute un nouveau commit qui annule un ancien — l'historique est préservé, sans danger sur les branches que d'autres ont déjà pullées. git reset recule le pointeur de branche — l'historique est réécrit, sûr uniquement sur des commits jamais poussés. Revert sur les branches partagées, reset pour le nettoyage local. reset --hard supprime en plus les modifications non commitées du working tree ; c'est lui qui dévore du vrai travail.
Quelle différence entre git revert et git reset ?
Les deux font ressembler ton projet à un commit qui n'aurait jamais existé. Ils se disputent sur le comment :
git revert <sha>calcule le patch inverse d'un commit et le commite. Ton historique grossit d'un commit qui dit « annule celui-là ». Le mauvais commit reste dans le log, suivi de son annulation. Les SHA de tout le reste ne bougent pas.git reset <sha>déplace le pointeur de la branche courante vers<sha>. Les commits d'après se détachent — toujours dans la base d'objets jusqu'au garbage collection, mais inatteignables depuis toute branche ougit log.
L'un ne réécrit rien, l'autre fait comme si rien ne s'était passé. Cette seule propriété décide lequel chaque situation réclame :
# Démo : un repo jetable mais exécutable
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"
# Revert : l'historique garde les deux commits, plus un troisième qui annule "two"
git revert --no-edit HEAD
git log --oneline # 3 commits : revert, two, one
# Reset : le pointeur de branche recule, "two" disparaît du log
git reset --hard HEAD~1
git log --oneline # 1 commit : one
Lance les deux et inspecte git log — l'asymétrie est toute la réponse.
Que font vraiment git reset --soft, --mixed et --hard ?
Les trois modes contrôlent où tes modifications survivent après le déplacement du pointeur :
| Mode | Pointeur de branche | Zone de staging | Working tree | Tes modifs |
|---|---|---|---|---|
--soft |
recule | conservées, stagées | intactes | tout est gardé |
--mixed (défaut) |
recule | désstagées | intactes | gardées, hors index |
--hard |
recule | réinitialisée | réinitialisé | supprimées |
Lectures concrètes de ce tableau :
git reset --soft HEAD~1— « j'ai commité trop tôt. » Tout retourne en zone de staging, prêt à recommiter, éventuellement avec du travail en plus.git reset HEAD~1(mixed) — « j'ai stagé les mauvais fichiers. » Les modifs survivent dans le working tree, rien n'est stagé. À coupler avecgit remove untracked filesquand tu veux aussi virer les fichiers égarés.git reset --hard HEAD~1— « ce commit et mes modifs non commitées sont à jeter. » Git ne demandera pas deux fois ; pas d'undo pour la partie working tree, sauf si tu l'avais stashé ou commité avant.
revert n'a pas de modes parce qu'il ne détruit jamais rien — il se contente d'ajouter un commit de compensation.
Quand utiliser git revert plutôt que git reset ?
Pose une seule question : quelqu'un d'autre a-t-il pullé ce commit ? Si oui, reset est hors table.
Réinitialiser une branche sur laquelle d'autres ont bâti, c'est réécrire l'historique partagé. Leur prochain pull rencontre des branches divergentes, et le « correctif » est en général un force-push qui transforme ta bévue en problème collectif. Revert ajoute un commit normal, donc un simple git pull fusionne proprement pour tout le monde — voilà pourquoi revert est la réponse par défaut sur main et sur les pull requests GitHub, où le bouton « Revert » existe précisément parce que réécrire l'historique d'une PR fusionnée n'est pas une option.
Reset, c'est pour la fenêtre d'avant le partage : le commit fait trente secondes plus tôt, l'erreur de staging, la branche d'expérimentation locale. Si la seule copie du travail vit sur ta machine, tu es libre de réarranger l'historique — c'est tout le sens de la fenêtre pré-push.
Il y a un cas intermédiaire : tu as poussé vers ta propre branche de feature et personne d'autre ne construit dessus. Un force-push après reset y est socialement acceptable, mais revert coûte quand même moins de coordination. Réserve les force-push aux branches que tu possèdes seul.
git revert est-il plus sûr que git reset sur les branches partagées ?
Oui, structurellement — pas seulement par convention :
- Revert produit un commit ordinaire ; la CI tourne dessus, le diff est relisible, et
git logexplique à ton futur toi pourquoi le changement a disparu. - Reset jette l'état intermédiaire en silence. Personne qui relit plus tard ne peut voir ce qui a été annulé ni quand, parce que le log montre une ligne droite qui ne l'a jamais contenu.
Deux arêtes tranchantes en pratique :
- Revert un commit de fusion demande
git revert -m 1 <sha>(garder la ligne du premier parent). Sans-m, git refuse et te laisse lire l'erreur. - Revert n'est pas une machine à remonter le temps : il annule le diff d'un seul commit. Si des commits ultérieurs ont touché les mêmes lignes, tu devras peut-être résoudre des conflits — c'est git qui te dit que l'annulation est emmêlée, une information utile, pas un dysfonctionnement.
Pour annuler spécifiquement ton tout dernier commit — en gardant ou en jetant ses changements — les options sont posées dans git undo last commit: keep the changes.
Et git restore, il sert à quoi ?
git restore (et git switch, son binôme) sont arrivés dans git 2.23 pour reprendre les jobs que reset faisait mal par surcharge de nom :
| Tâche | L'ancienne façon | La façon claire |
|---|---|---|
| Jeter les modifs d'un fichier dans le working tree | git checkout -- file / git reset --hard |
git restore file |
| Désstager un fichier | git reset HEAD file |
git restore --staged file |
| Déplacer une branche vers un autre commit | git reset <sha> |
git reset <sha> (pas de remplaçant — ça reste) |
Donc la répartition moderne : restore répare les fichiers, reset déplace les branches, revert annule les commits publiés. L'autocomplétion montre qu'on cherche « git revert vs reset vs restore » ensemble, et pour de bonnes raisons — c'est un seul modèle mental éclaté sur trois commandes. Les anciennes formes de git checkout marchent toujours partout ; les nouvelles t'évitent juste d'envoyer toute une branche au destructeur quand tu voulais désstager un fichier.
Si ton but est de copier un bon commit vers l'avant plutôt que d'effacer un mauvais, c'est le travail de cherry-pick — voir git cherry-pick: multiple commits, branches, conflicts.
Récap pour trancher vite
- Commit public (poussé, pullé par d'autres) →
git revert <sha>. - Commit purement local, tu veux le refaire →
git reset --softou--mixed, puis recommit. - Local seulement, et tu le veux disparu avec le bazar non commité →
git reset --hard, après un regard honnête sur ce que le working tree contient d'autre. - Un seul fichier abîmé →
git restore <file>, et laisse la branche tranquille.
La seule entrée vraiment dangereuse de cette liste est --hard — tout le reste se rattrape avec git reflog. Les pertes permanentes dans git sont étroites et demandent presque toujours que tu l'écrives explicitement ; l'habitude qui vaut la peine, c'est une demi-seconde de pause avant --hard, pas l'évitement des outils tranchants de git.