« J'ai tout perdu » (spoiler : probablement pas)
Respirez un grand coup. Si le travail qui vous manque a été committé ne serait-ce qu'une fois -- même sur la mauvaise branch, même dans un commit que vous avez ensuite « supprimé » -- il est presque certainement encore dans votre dépôt en ce moment même. Git est bien meilleur pour conserver les choses que pour les perdre, et les dix prochaines minutes de lecture se termineront très probablement avec votre code de retour à l'écran.
Voici la vérité rassurante sur le fonctionnement interne de Git : Git ne supprime presque jamais rien immédiatement. Chaque commit que vous créez est stocké comme un objet dans le répertoire .git, identifié par son SHA. Quand vous lancez git reset --hard, supprimez une branch ou réécrivez l'historique avec un rebase, Git n'efface pas ces objets commit. Il ne fait que déplacer des pointeurs -- les noms de branches et HEAD -- pour qu'ils ne pointent plus vers eux. Les commits deviennent « inaccessibles », c'est-à-dire qu'aucune branch ni aucun tag n'y mène plus, mais les objets eux-mêmes restent sur le disque pendant des semaines avant que le garbage collection n'envisage même d'y toucher.
Le problème n'est donc pas que vos commits ont disparu. Le problème est que vous n'avez plus de nom pour les désigner. Il vous faut le SHA. Et Git tient un journal qui enregistre discrètement chaque SHA par lequel votre dépôt est passé : le reflog.
Ce qu'est vraiment le reflog
Le reflog (reference log) est un journal local, propre à chaque machine, des positions successives de HEAD et de la pointe de chaque branch. Chaque fois que HEAD bouge -- parce que vous faites un commit, un checkout, un merge, un rebase, un reset ou un amend -- Git ajoute une ligne à ce journal. C'est la boîte noire de votre dépôt.
Pour le consulter, lancez :
git reflog
Le résultat ressemble à ceci :
e4f5a6b HEAD@{0}: reset: moving to HEAD~3
a1b2c3d HEAD@{1}: commit: add payment validation
9f8e7d6 HEAD@{2}: commit: refactor checkout flow
3c4d5e6 HEAD@{3}: checkout: moving from main to feature/payments
Lisez-le de haut en bas, du plus récent au plus ancien. Chaque ligne indique le SHA vers lequel HEAD pointait, une référence positionnelle comme HEAD@{1}, l'opération qui a déplacé HEAD et une courte description. La syntaxe HEAD@{n} signifie « là où HEAD se trouvait il y a n mouvements » : HEAD@{0} est votre position actuelle, HEAD@{1} est une opération en arrière, et ainsi de suite. Vous pouvez utiliser ces références dans n'importe quelle commande qui accepte un commit, et c'est exactement ce qui rend les opérations de sauvetage aussi directes.
Dans l'exemple ci-dessus, l'histoire est claire : vous avez fait deux commits (HEAD@{2} et HEAD@{1}), puis un reset a ramené HEAD trois commits en arrière. Ces deux commits ne sont pas perdus -- ils sont juste là, à a1b2c3d.
HEAD n'est pas la seule référence à avoir un journal. Chaque branch tient le sien :
git reflog show feature/payments
Cela affiche toutes les positions occupées par la pointe de feature/payments -- utile quand vous voulez l'historique d'une seule branch sans le bruit de tous les checkouts que vous avez jamais faits.
Scénarios de sauvetage, pas à pas
1. Vous êtes allé trop loin avec git reset --hard
Vous vouliez annuler un commit et vous en avez effacé trois, ou vous avez fait un reset vers complètement le mauvais endroit. Le reflog a enregistré où vous étiez juste avant le reset, et cette position est HEAD@{1} :
git reset --hard HEAD@{1}
C'est toute la réparation. Le reset lui-même n'était qu'un mouvement de HEAD de plus, donc ramener HEAD à sa position précédente restaure tout. Si vous avez fait d'autres choses depuis le mauvais reset, lancez d'abord git reflog, trouvez la ligne juste avant l'entrée du reset, et faites un reset vers ce SHA à la place. Pour un tour d'horizon plus large des stratégies d'annulation, consultez comment annuler un commit Git.
2. Vous avez supprimé une branch
Supprimer une branch supprime le pointeur, pas les commits. Si la branch a récemment fait l'objet d'un checkout ou d'un commit sur cette machine, sa pointe est dans le reflog de HEAD. Trouvez-la :
git reflog | grep "feature/payments"
Cherchez le dernier commit fait sur cette branch, ou la dernière entrée checkout: moving from feature/payments -- le SHA sur cette ligne est la pointe de la branch. Puis recréez la branch en la faisant pointer dessus :
git branch feature/payments a1b2c3d
La branch est de retour, identique au moment de sa suppression. En bonus : quand vous supprimez une branch, Git affiche le SHA de sa pointe (Deleted branch feature/payments (was a1b2c3d)) -- si ce message est encore dans l'historique de votre terminal, vous pouvez vous passer complètement de la recherche dans le reflog.
3. Un rebase a mal tourné
Un rebase réécrit les commits, et au milieu d'un rebase truffé de conflits, on peut avoir l'impression que sa branch est abîmée au-delà de toute réparation. Ce n'est pas le cas : l'état d'avant le rebase est dans le reflog. Si le rebase est encore en cours, la sortie la plus propre est git rebase --abort. S'il est déjà terminé et que vous détestez le résultat, trouvez l'entrée d'avant son démarrage :
git reflog
# cherchez : "rebase (start)" -- l'entrée juste EN DESSOUS
# est la position de votre branch avant le rebase
git reset --hard HEAD@{5}
(Remplacez {5} par la position qu'occupe l'entrée pré-rebase dans votre reflog.) Votre branch est exactement comme avant. Si les rebases vous mettent régulièrement dans cette situation, notre guide du rebase interactif sans crainte explique comment les rendre routiniers plutôt que risqués.
4. Un commit a disparu après un --amend
git commit --amend ne modifie pas un commit -- il en crée un nouveau et y déplace la branch. L'original est inaccessible mais intact, et il se trouve à HEAD@{1} juste après l'amend. Pour l'inspecter :
git show HEAD@{1}
Si vous avez amendé le mauvais commit ou avez besoin de récupérer l'original, faites un reset dessus ou un cherry-pick vers l'endroit où il doit aller.
5. Vous avez committé sur un HEAD détaché, puis changé de branch
Vous avez fait un checkout d'un commit ou d'un tag précis, créé des commits dans cet état de HEAD détaché, puis fait un checkout d'une branch -- et vos commits se sont apparemment évaporés. Git vous avertit d'ailleurs au moment du changement, en affichant le SHA orphelin. Le reflog l'a de toute façon :
git reflog
# trouvez le dernier commit que vous avez fait avant
# l'entrée "checkout: moving from <sha> to <branch>"
git branch rescued-work e4f5a6b
Vos commits détachés vivent maintenant sur une vraie branch, et vous pouvez les merger ou les rebaser où ils doivent aller.
Inspecter avant de restaurer
Avant de pointer quoi que ce soit vers un SHA récupéré, regardez-le. Confirmez que c'est bien le commit que vous croyez :
git show a1b2c3d
Cela affiche le message du commit, l'auteur, la date et le diff complet. Si vous voulez parcourir le reflog avec les détails complets de chaque commit plutôt que des entrées d'une ligne, utilisez le mode reflog du log :
git log -g
Et voici l'habitude la plus sûre de toutes : au lieu de faire un reset de votre branch actuelle vers un commit récupéré, créez d'abord une branch temporaire dessus :
git branch rescue a1b2c3d
Le travail récupéré a désormais un nom permanent et accessible. Rien n'a changé sur votre branch actuelle, le garbage collection ne pourra jamais toucher les commits sauvés, et vous pouvez inspecter, comparer et merger à votre rythme. Une branch ne coûte rien ; utilisez-en une dès que vous n'êtes pas sûr à 100 %.
Les limites, honnêtement
Le reflog est un filet de sécurité remarquable, mais il a de vraies limites, et mieux vaut les connaître avant d'en avoir besoin :
- Il est strictement local. Le reflog vit sur votre machine et n'est jamais pushé, pullé ni cloné. Un clone tout frais démarre avec un reflog vide. Si vous avez perdu des commits sur votre laptop, c'est le reflog de votre laptop qui compte -- le clone d'un collègue ne peut pas vous aider, et la copie du serveur non plus.
- Les entrées expirent. Par défaut, les entrées du reflog pour les commits encore accessibles depuis une branch expirent après 90 jours, et celles des commits inaccessibles après 30 jours. Passé ce délai, le garbage collection peut supprimer les objets inaccessibles pour de bon. En pratique, c'est largement suffisant -- mais cela signifie que le reflog sauve l'erreur du mois dernier, pas celle de l'année dernière.
- Il ne peut pas récupérer ce qui n'a jamais été committé. Les modifications non committées détruites par
git reset --hardougit checkout -- <file>n'ont jamais été des objets Git, donc aucun journal ne les a enregistrées. La seule exception partielle : les fichiers qui ont au moins été mis en staging avecgit addexistent comme objets blob, etgit fsck --lost-foundpeut extraire ces blobs orphelins dans.git/lost-found/-- du contenu sans nom de fichier ni historique, mais mieux que rien. C'est un dernier recours, pas un workflow. - Les stashes supprimés ont leur propre issue de secours. Un stash que vous avez supprimé avec drop ou clear n'est pas dans le reflog de HEAD, mais les commits du stash survivent souvent comme objets orphelins.
git fsck --unreachable | grep commitsuivi degit showsur les candidats permet de les retrouver. Si vous utilisez beaucoup le stash, notre guide de git stash couvre les workflows les plus sûrs.
Prévention : rendre les prochaines paniques ennuyeuses
Tous les scénarios ci-dessus avaient un prérequis : le travail était committé. D'où le seul conseil de prévention qui compte vraiment : committez tôt et committez souvent. Les petits commits de travail en cours, même brouillons, ne coûtent rien -- vous pourrez les squasher, les reformuler ou les réordonner plus tard. Dès qu'une chose est committée, elle est dans la base d'objets et le reflog veille sur elle. Tant qu'elle ne l'est pas, vous comptez sur la chance.
Pour les changements de contexte rapides où un commit semble trop lourd, utilisez le stash plutôt que de jongler avec des modifications non committées entre les checkouts -- un stash est lui aussi un véritable objet que Git peut retrouver.
Où GitSquid intervient (et où la CLI gagne)
Réponse honnête : le reflog est l'un de ces domaines où la ligne de commande est le bon outil. GitSquid n'a pas de navigateur de reflog, et quand vous avez besoin de git reflog, ouvrez un terminal et utilisez-le -- les commandes de cet article sont le chemin de récupération.
Ce que GitSquid fait à la place, c'est rendre le workflow environnant plus sûr, pour que vous ayez moins souvent besoin du reflog. La timeline de fichier (clic droit sur n'importe quel fichier puis Show history) vous donne un historique visuel par fichier, qui répond souvent à « où est passé ce code ? » sans aucune récupération. Les opérations destructrices comme le reset et la suppression de branch passent par des menus contextuels explicites sur le graphe de commits, vous voyez donc exactement ce que vous vous apprêtez à faire avant de le faire. Et quand vous reconstituez « ce qui vient de se passer », le journal de commandes (Cmd/Ctrl+Shift+L) affiche chaque commande Git que GitSquid a exécutée sur votre dépôt, avec les arguments complets et les codes de sortie -- précisément les preuves que vous voulez à côté d'un reflog pour reconstituer un incident.
Référence rapide
| Situation | Commande |
|---|---|
| Voir où HEAD est passé | git reflog |
| Voir où la pointe d'une branch est passée | git reflog show <branch> |
Annuler un mauvais reset --hard |
git reset --hard HEAD@{1} |
| Restaurer une branch supprimée | git branch <name> <sha> |
| Annuler un rebase terminé | git reset --hard HEAD@{n} (entrée pré-rebase) |
| Inspecter un commit récupéré | git show <sha> |
| Parcourir le reflog en détail | git log -g |
| Mettre une récupération à l'abri | git branch rescue <sha> |
| Dernier recours pour les fichiers stagés mais jamais committés | git fsck --lost-found |
Le reflog transforme « j'ai tout perdu » en « j'ai égaré un pointeur pendant dix minutes ». Committez souvent, consultez le reflog avant de paniquer, et mettez vos récupérations à l'abri sur une branch temporaire avant de toucher à quoi que ce soit d'autre. Et si vous voulez un client Git qui rend les opérations destructrices explicites et garde un journal complet de chaque commande exécutée, Téléchargez GitSquid gratuitement -- il ne remplacera pas votre reflog, mais il vous aidera à en avoir moins souvent besoin.