Tutoriel

git reflog : comment récupérer des commits et des branches perdus

Mis à jour: · Skillaria.Top

git reflog est un journal local où Git enregistre chaque déplacement de HEAD et des branches : commits, reset, checkout, rebase, merge. Repérez dans git reflog l'entrée antérieure à l'erreur, par exemple HEAD@{2}, puis restaurez l'état avec git reset --hard HEAD@{2} ou créez une branche avec git branch rescue HEAD@{2}.

git reflog est le journal des références (reference log) : Git y consigne chaque déplacement de HEAD et des extrémités de branches. Un commit, un changement de branche, un reset, un rebase ou un merge — et une ligne apparaît dans le reflog avec le hash vers lequel pointait la référence après l'opération. Même un commit qu'aucune branche ne référence plus peut donc être retrouvé et récupéré.

L'essentiel à retenir : le reflog est local. Il n'existe que dans votre clone, il n'est pas envoyé sur le serveur et n'arrive pas avec git clone. En revanche, dans votre propre dépôt, il vous sauve de presque toutes les erreurs d'historique.

Comment lire la sortie de git reflog

git reflog
3c4d5e6 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9a8b7c6 HEAD@{1}: commit: Ajouter la validation de l'e-mail
1f2e3d4 HEAD@{2}: commit: Déplacer la config dans un fichier séparé
3c4d5e6 (HEAD -> main) HEAD@{3}: checkout: moving from feature to main
7b6a5c4 (feature) HEAD@{4}: commit: Brouillon du formulaire de connexion

Chaque ligne correspond à un état de HEAD, le plus récent en haut :

  • le hash — le commit vers lequel pointait HEAD après l'opération ;
  • HEAD@{n} — « où était HEAD il y a n déplacements » ; c'est une référence à part entière, utilisable dans n'importe quelle commande ;
  • la description — le type d'opération (commit, reset, checkout, rebase, merge, pull, cherry-pick, commit (amend)) et les détails.

Dans l'exemple, deux commits — « Déplacer la config » et « Ajouter la validation » — ont été créés, puis reset HEAD~2 a fait reculer la branche. Ils ne figurent plus dans git log, mais ils sont toujours dans le reflog.

Le reflog d'une branche précise

Chaque branche a son propre journal. Il est plus court et plus utile quand vous avez beaucoup navigué entre branches :

git reflog show feature
git reflog show origin/main   # comment la branche distante a évolué lors des fetch

Références temporelles

Au lieu d'un numéro, vous pouvez indiquer un moment — Git retrouve où pointait la référence à cet instant :

git show main@{yesterday}
git diff main@{"2 hours ago"} main
git log -g --since="1 day ago" --oneline

L'option -g (--walk-reflogs) de git log parcourt le reflog au lieu des liens de parenté et permet d'utiliser les formats et filtres habituels. Pour afficher les dates, git reflog --date=iso est pratique.

Comment récupérer des commits après git reset --hard

Le cas le plus courant : vous avez lancé git reset --hard sur le mauvais commit et votre travail a « disparu ». En réalité, les commits sont toujours là — ils ne sont simplement plus visibles depuis les branches.

  1. Ouvrez le journal : git reflog.
  2. Repérez la ligne juste avant reset: moving to ... — c'est l'état précédant l'erreur. Dans l'exemple ci-dessus, HEAD@{1} avec le hash 9a8b7c6.
  3. Vérifiez que c'est bien le bon : git log --oneline -5 HEAD@{1}.
  4. Restaurez la branche : git reset --hard HEAD@{1}.
git reset --hard HEAD@{1}
HEAD is now at 9a8b7c6 Ajouter la validation de l'e-mail

Attention : les numéros se décalent après chaque opération. Ce qui était HEAD@{1} devient HEAD@{2} après le reset. Il est donc plus fiable de copier le hash que le numéro.

Limite importante : le reflog ne conserve que ce qui a été commité. Les modifications non commitées effacées par reset --hard ou git checkout -- fichier ne se récupèrent pas via le reflog. Il reste un peu d'espoir si les fichiers avaient été ajoutés à l'index — voir plus bas.

Comment restaurer une branche supprimée

Vous avez supprimé une branche avec git branch -D feature, et elle contenait des commits non poussés. Le journal de la branche disparaît avec elle, mais celui de HEAD reste — et HEAD est passé par ses commits quand vous travailliez dessus.

git reflog | grep feature
7b6a5c4 HEAD@{4}: commit: Brouillon du formulaire de connexion
3c4d5e6 HEAD@{3}: checkout: moving from feature to main

# Le dernier commit de la branche est celui depuis lequel vous l'avez quittée
git branch feature 7b6a5c4

Cherchez la ligne checkout: moving from feature to ... : le hash de l'entrée précédente (plus bas dans la liste) est le dernier état de la branche. D'ailleurs, lors de la suppression, Git affiche lui-même le hash : Deleted branch feature (was 7b6a5c4). — si le terminal est encore ouvert, cela suffit.

Comment annuler un rebase raté

Rebase réécrit les commits, et si vous avez mal résolu les conflits en cours de route, le résultat peut être pire que l'original. Le reflog conserve l'état d'avant le rebase.

git reflog
e1f2a3b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e1f2a3b HEAD@{1}: rebase (pick): Brouillon du formulaire de connexion
c4d5e6f HEAD@{2}: rebase (start): checkout main
7b6a5c4 HEAD@{3}: commit: Brouillon du formulaire de connexion

git reset --hard HEAD@{3}

Il vous faut l'entrée juste avant rebase (start). Il existe aussi un raccourci : avant les opérations risquées (rebase, merge, reset), Git enregistre la position précédente dans la référence ORIG_HEAD, donc juste après un rebase git reset --hard ORIG_HEAD fonctionne. Mais ORIG_HEAD est écrasé par l'opération suivante du même type, alors que le reflog garde tout l'historique.

Comment retrouver un commit d'avant git commit --amend

--amend ne modifie pas le commit : il en crée un nouveau à sa place. L'ancien reste dans le reflog :

git reflog
5a6b7c8 HEAD@{0}: commit (amend): Ajouter la validation de l'e-mail
9a8b7c6 HEAD@{1}: commit: Ajouter la validation de l'e-mail

git diff HEAD@{1} HEAD          # ce que l'amend a changé
git reset --soft HEAD@{1}       # revenir à l'ancien commit, les changements de l'amend restent dans l'index

Récupération sans risque : une branche plutôt qu'un reset

Si vous n'êtes pas sûr du commit qu'il vous faut, ne faites pas de reset --hard à l'aveugle. Créez une branche de secours — elle ne casse rien et protège le commit trouvé de la suppression :

git branch rescue HEAD@{5}
git log --oneline rescue
git switch rescue

Ensuite, les commits de rescue peuvent être récupérés via cherry-pick ou fusionnés, et la branche supprimée une fois que tout est rentré dans l'ordre.

Combien de temps le reflog est-il conservé

Les entrées ne sont pas éternelles. Elles sont nettoyées par git gc, que Git lance lui-même de temps en temps. Les délais dépendent de ces réglages :

RéglagePar défautSignification
gc.reflogExpire90 joursDurée de vie des entrées dont les commits sont accessibles depuis les branches actuelles
gc.reflogExpireUnreachable30 joursDurée de vie des entrées dont les commits ne sont accessibles depuis aucune branche — justement les « perdus »
gc.pruneExpire2 semainesDélai avant suppression des objets inaccessibles eux-mêmes, que plus rien ne référence

Conclusion pratique : un commit perdu reste accessible via le reflog pendant environ un mois de façon garantie. Ne repoussez pas la récupération et ne lancez pas manuellement git gc --prune=now ou git reflog expire --expire=now --all tant que vous n'avez pas récupéré ce qu'il vous faut.

Si le reflog ne suffit pas : git fsck

Il arrive que le commit ne figure pas dans le reflog : par exemple, il a été créé par git stash, puis le stash a été supprimé avec git stash drop, ou bien les modifications n'existaient que dans l'index. Cherchez alors les objets « pendants » :

git fsck --lost-found
dangling commit 2b3c4d5e6f...
dangling blob 8e9f0a1b2c...

git show 2b3c4d5e6f            # voir le contenu
git branch recovered 2b3c4d5e6f

Les commits pendants issus d'un stash supprimé portent le message « WIP on ... ». Les blobs pendants sont le contenu de fichiers ajoutés un jour avec git add mais jamais commités. Ils n'ont pas de nom de fichier, il faudra les identifier par leur contenu, mais c'est parfois ainsi qu'on sauve un travail après un reset --hard. Avec l'option --lost-found, Git les copie en plus dans .git/lost-found/.

Ce que le reflog ne sait pas faire

  • Il ne fonctionne pas d'une machine à l'autre. Si un collègue a fait un force push qui a écrasé vos commits sur le serveur, cherchez-les dans le reflog de quelqu'un qui les avait en local — par exemple git reflog show origin/main chez quiconque a fait un fetch avant l'écrasement.
  • Il ne voit pas ce qui n'est pas commité. La meilleure assurance avant une opération risquée reste donc un commit ou git stash.
  • Il ne garde pas l'historique indéfiniment. Voir les délais ci-dessus, et un clone tout neuf démarre avec un journal vide.

En pratique : s'assurer avant une opération risquée

Le reflog vous sauve, mais la récupération demande de l'attention : il faut trouver la bonne entrée parmi des dizaines de lignes similaires. Il est bien plus simple de poser un repère à l'avance. Avant un rebase, un gros reset ou des expériences sur l'historique, créez une branche ou un tag temporaire :

git branch backup/before-rebase
git rebase -i main
# quelque chose a mal tourné
git reset --hard backup/before-rebase
# tout s'est bien passé
git branch -D backup/before-rebase

Ça coûte une commande et vous évite de chercher un hash. Le reflog reste la solution de secours pour les fois où vous pensez à l'assurance trop tard.

Comment trouver rapidement la bonne entrée

Dans un long journal, le filtrage aide. Cherchez par type d'opération ou par texte du message de commit, et dans les cas douteux, regardez le contenu :

git reflog | grep -E "reset|rebase \(start\)"
git log -g --grep="validation" --oneline
git show --stat HEAD@{7}

La commande git log -g avec --grep cherche dans les messages des commits du reflog, et git show --stat montre les fichiers touchés — de quoi reconnaître facilement le bon commit, même si son message était aussi peu parlant que « wip ».

En résumé

git reflog est le filet de sécurité de Git : tout ce qui a été commité dans votre dépôt ces dernières semaines peut être retrouvé et récupéré. La démarche est toujours la même : ouvrir le journal, trouver l'entrée antérieure à l'erreur, la vérifier avec git log ou git show, puis créer une branche ou faire un reset --hard. Restore, reset, revert et la récupération après erreur sont abordés en pratique dans le cours Git.

Questions fréquentes

Quelle différence entre git reflog et git log ?

git log suit les liens de parenté des commits et montre l'historique d'une branche. git reflog montre où HEAD et les branches se sont déplacés dans votre dépôt, y compris les commits qui ne figurent plus dans l'historique des branches.

Le reflog est-il envoyé sur GitHub ou GitLab ?

Non, le reflog est stocké uniquement dans le dossier local .git/logs et n'est transmis ni par push ni par clone.

Peut-on vider le reflog ?

Oui, avec git reflog expire --expire=now --all suivi de git gc --prune=now, mais il devient alors impossible de récupérer les commits perdus.

Où le reflog est-il stocké physiquement ?

Dans les fichiers texte .git/logs/HEAD et .git/logs/refs/heads/nom-de-branche, à raison d'une ligne par déplacement.

Le reflog fonctionne-t-il dans un dépôt bare sur le serveur ?

Par défaut, le reflog est désactivé dans les dépôts bare (core.logAllRefUpdates), on ne peut donc pas compter dessus côté serveur.

Apprenez ce sujet en vidéo

Ce sujet est traité en détail avec de la pratique dans le cours « Git de zéro à développeur confirmé ».

Voir le cours