git reset ou git revert : annuler un commit sans casser l'historique de l'équipe
git reset déplace le pointeur de branche vers un autre commit et réécrit l'historique ; il ne convient donc qu'aux commits locaux pas encore poussés. git revert crée un nouveau commit avec les changements inverses sans toucher à l'historique : c'est la façon sûre d'annuler un commit déjà présent sur le serveur.
En bref : git reset fait reculer la branche et fait comme si les commits superflus n'avaient jamais existé. git revert laisse l'historique tel quel et ajoute un nouveau commit qui annule les changements de l'ancien. Le premier convient au travail local, le second à tout ce qui est déjà parti dans le dépôt partagé. Si vous ne retenez que cette règle, vous éviterez la plupart des accidents avec Git.
Voyons ci-dessous comment fonctionnent les deux commandes, ce qui distingue les modes de reset, comment annuler un commit après un push, comment récupérer des commits « perdus » et ce que vient faire git clean là-dedans.
Les trois zones touchées par les commandes d'annulation
Pour comprendre la différence, rappelez-vous les trois zones de Git : le répertoire de travail (les fichiers sur le disque), l'index (staging area, ce qui entrera dans le prochain commit) et l'historique (les commits pointés par la branche et HEAD). Chaque commande d'annulation agit sur sa propre combinaison de ces zones.
| Commande | Historique | Index | Répertoire de travail | Sûre sur une branche partagée |
|---|---|---|---|---|
git restore fichier | non | non | oui | oui |
git restore --staged fichier | non | oui | non | oui |
git reset --soft | oui | non | non | non |
git reset --mixed (par défaut) | oui | oui | non | non |
git reset --hard | oui | oui | oui | non |
git revert | ajoute un commit | — | — | oui |
git clean | non | non | supprime les fichiers non suivis | oui, mais irréversible |
Comment fonctionne git reset
La commande git reset <commit> déplace le pointeur de la branche courante (et HEAD avec lui) sur le commit indiqué. Les commits « après » ne sont pas supprimés physiquement, mais ne sont plus visibles dans la branche. Le mode détermine ce qu'il advient de l'index et des fichiers.
git reset --soft : annuler un commit en gardant les changements dans l'index
La branche recule, mais l'index et les fichiers restent intacts. Tous les changements des commits annulés se retrouvent prêts à être commités. C'est la méthode idéale pour réécrire un message ou regrouper les derniers commits en un seul.
git reset --soft HEAD~3
git commit -m "feat: formulaire d'inscription"
Les trois derniers commits n'en font plus qu'un, le contenu des fichiers n'a pas changé.
git reset --mixed : annuler le commit et l'index
C'est le mode par défaut : git reset HEAD~1 équivaut à git reset --mixed HEAD~1. La branche recule, l'index est vidé, et les changements restent dans le répertoire de travail, non indexés. Pratique quand vous avez commité trop de choses et voulez redécider quoi ajouter, et par quelles portions.
git reset HEAD~1
git status
# Changes not staged for commit:
# modified: app.py
# modified: config.yml
git reset --hard : revenir complètement en arrière
La branche, l'index et le répertoire de travail sont ramenés à l'état du commit indiqué. Toutes les modifications non commitées des fichiers suivis sont perdues sans avertissement. C'est le seul mode de reset qui peut réellement détruire votre travail.
git reset --hard HEAD~1 # jeter le dernier commit et toutes les modifications
git reset --hard origin/main # aligner la branche sur l'état du serveur
Avant un --hard, lancez git status, et git stash s'il y a quelque chose de précieux. Ce qui est commité se récupère (voir plus bas) ; ce qui ne l'est pas, non.
git reset sur un seul fichier
Avec un chemin, reset ne déplace pas la branche et modifie seulement l'index : git reset app.py retire le fichier des changements indexés. L'équivalent moderne, plus parlant, est git restore --staged app.py. Les options --soft et --hard ne fonctionnent pas avec des chemins.
Comment fonctionne git revert
git revert <commit> calcule les changements du commit indiqué, les applique en sens inverse et crée un nouveau commit. Les lignes ajoutées sont supprimées, les lignes supprimées reviennent. L'historique avance, rien n'est réécrit, et vos collègues n'ont rien à réparer de leur côté.
git log --oneline
# a3f9c21 (HEAD -> main) docs: README
# 7be41d0 feat: mise en cache des réponses
# 1c02e8f fix: timeout des requêtes
git revert 7be41d0
# [main 5d8e2aa] Revert "feat: mise en cache des réponses"
# 2 files changed, 3 insertions(+), 41 deletions(-)
Git ouvre l'éditeur avec un message du type Revert "..." et la ligne This reverts commit 7be41d0.... Bonne pratique : ajouter pourquoi le changement est annulé.
Options utiles de revert
--no-edit— accepter le message standard sans éditeur.-n(--no-commit) — appliquer les changements inverses dans l'index sans créer de commit. Permet d'annuler plusieurs commits en un seul commit.git revert A..B— annuler une plage de commits (chacun par un commit de revert séparé, du plus récent au plus ancien).-m 1— obligatoire pour un commit de merge : indique quel parent est la ligne principale.
git revert --no-commit HEAD~3..HEAD
git commit -m "Revert : annulation des trois commits de l'expérimentation"
Revert d'un commit de merge
Une fusion a deux parents, et Git ne sait pas par rapport auquel calculer les changements « inverses » : sans -m, il renvoie l'erreur is a merge but no -m option was given. En général, la branche principale est le premier parent :
git revert -m 1 9f1e2ab
Le piège : si vous voulez plus tard fusionner à nouveau la même branche, Git considérera ses commits comme déjà fusionnés et n'apportera pas les changements. Solution : faire d'abord un revert du commit de revert lui-même, puis ajouter les nouvelles modifications.
git reset vs git revert : que choisir
| Critère | git reset | git revert |
|---|---|---|
| Effet sur l'historique | le réécrit (les commits disparaissent de la branche) | ajoute un nouveau commit |
| Commit déjà poussé | push --force nécessaire, casse le travail des collègues | git push ordinaire |
| Annuler un commit au milieu de l'historique | seulement avec tous les suivants | possible de façon ciblée |
| Trace dans l'historique | aucune | oui, on voit qui a annulé et pourquoi |
| Scénario type | nettoyer ses commits locaux avant un push | annuler dans main un commit qui a cassé la prod |
Règle simple : si quelqu'un d'autre que vous a vu le commit — revert ; sinon — reset. Exception : votre branche feature personnelle que personne n'a récupérée ; là, réécrire l'historique et faire git push --force-with-lease est acceptable.
Pourquoi reset --hard sur une branche partagée est une mauvaise idée
Imaginez : vous faites git reset --hard HEAD~2 sur main puis git push --force. Vos collègues ont toujours ces deux commits. Au prochain git pull, Git tentera de fusionner leur branche avec la vôtre, et les commits « supprimés » reviendront, accompagnés d'un commit de merge parasite. Pire : si quelqu'un a poussé ses propres commits par-dessus entre-temps, le force push les écrasera purement et simplement.
C'est pourquoi le force push est généralement interdit sur les branches protégées (main, release) dans les réglages de l'hébergeur. Si la réécriture est inévitable, utilisez git push --force-with-lease : il refuse si le serveur contient des commits que vous n'avez pas vus.
Scénarios d'annulation courants
Annuler le dernier commit pas encore poussé
git reset --soft HEAD~1 # changements dans l'index, prêts à être recommités
git reset HEAD~1 # changements dans les fichiers, index vide
git reset --hard HEAD~1 # changements jetés
S'il s'agit seulement de corriger le message ou d'ajouter un fichier oublié, reset est inutile : git add oublie.txt puis git commit --amend.
Annuler un commit après un push
git revert <hash>
git push
C'est la réponse à la recherche fréquente « git reverse commit » : il n'existe pas de commande git reverse dans Git, c'est bien git revert qui crée le commit « inverse ».
Ramener une branche à l'état du serveur
git fetch origin
git reset --hard origin/main
Utile quand la branche locale s'est embrouillée après un rebase raté ou des expérimentations. Tous les commits locaux absents du serveur disparaîtront de la branche.
Récupérer des commits après un reset malheureux
Git consigne chaque déplacement de HEAD dans le journal reflog. Après un reset, les commits restent dans la base d'objets au moins plusieurs semaines (par défaut, les entrées reflog inaccessibles sont conservées 30 jours).
git reflog
# 1c02e8f HEAD@{0}: reset: moving to HEAD~2
# a3f9c21 HEAD@{1}: commit: docs: README
# 7be41d0 HEAD@{2}: commit: feat: mise en cache des réponses
git reset --hard a3f9c21 # ou HEAD@{1}
Le reflog est local : il n'existe que dans votre clone et n'est pas transmis au serveur.
git clean : supprimer les fichiers non suivis
git reset --hard ne touche pas aux fichiers que Git ne suit pas : nouveaux fichiers jamais ajoutés à l'index, artefacts de build, logs. Pour eux, il y a git clean. La commande est irréversible : les fichiers supprimés ne passent ni par le reflog ni par la corbeille.
git clean -n # dry run : afficher ce qui sera supprimé
git clean -f # supprimer les fichiers non suivis
git clean -fd # idem, plus les dossiers non suivis
git clean -fdx # plus les fichiers ignorés (.gitignore) : node_modules, dist
git clean -i # sélection interactive
Sans -f, Git refuse par défaut d'agir (réglage clean.requireForce). Commencez toujours par -n, surtout avec -x : il efface aussi votre .env local s'il est ignoré.
Combinaison pour revenir entièrement au dernier commit : git reset --hard && git clean -fd.
Erreurs fréquentes
- reset --hard au lieu de --soft. Le reflog sauve ce qui était commité ; le reste est perdu. L'habitude de faire
git stashavant d'expérimenter règle le problème. - revert sans comprendre les dépendances. Si des commits plus récents ont modifié les mêmes lignes, revert provoque un conflit. Résolvez-le normalement : modifiez les fichiers,
git add,git revert --continue; si vous changez d'avis —git revert --abort. - Confusion entre restore et reset. Pour annuler des modifications dans un seul fichier, pas besoin de reset toute la branche :
git restore fichier. - force push sur main. Préférez revert et un push ordinaire, même si l'historique s'allonge un peu.
Conclusion
git reset est l'outil de votre historique personnel, pas encore publié : --soft garde les changements dans l'index, --mixed dans les fichiers, --hard jette tout. git revert est l'annulation sûre sur les branches partagées, qui laisse une trace honnête dans l'historique. git clean achève le nettoyage des fichiers non suivis, sans droit à l'erreur. Les scénarios d'annulation avec restore, reset et revert sont pratiqués dans le cours Git.
Questions fréquentes
git reset supprime-t-il les commits définitivement ?
Non, les commits restent dans la base d'objets et accessibles via git reflog jusqu'à leur suppression par le ramasse-miettes — par défaut après 30 jours pour les entrées inaccessibles.
Peut-on annuler un git revert ?
Oui, un commit de revert est un commit ordinaire : on peut l'annuler par un autre git revert ou, s'il n'est pas poussé, le retirer avec git reset.
Quelle différence entre git reset et git checkout ?
git reset déplace le pointeur de la branche courante, tandis que git checkout (ou git switch) fait passer HEAD sur une autre branche ou un autre commit sans modifier la branche elle-même.
Que signifient HEAD~1 et HEAD^ ?
Tous deux désignent le parent du commit courant ; HEAD~3 est le troisième ancêtre en suivant les premiers parents, et HEAD^2 le second parent d'un commit de merge.
Comment annuler un git clean ?
Impossible avec Git : les fichiers non suivis supprimés n'étaient pas stockés dans le dépôt. Lancez donc toujours git clean -n d'abord.
Ce sujet est traité en détail avec de la pratique dans le cours « Git de zéro à développeur confirmé ».
Voir le cours