Git avance - 07 - Reflog et recovery : rien n'est jamais perdu dans git

Le reflog git garde tout. Comment récupérer des commits perdus, des branches supprimees et des rebases rates.

07 - Reflog et recovery : rien n'est jamais perdu dans git

Ce que tu vas apprendre

  • Ce qu'est le reflog et pourquoi c'est ton filet de sécurité
  • Récupérer une branche supprimee
  • Annuler un rebase foireux
  • Retrouver un commit amende par erreur

Prerequisites


Le reflog : la boîte noire de git

Chaque fois que HEAD bouge dans ton repo (commit, checkout, rebase, merge, reset, pull...), git enregistre le mouvement dans le reflog. C'est un journal chronologique de tout ce qui s'est passe. Pendant 90 jours.

bashgit reflog
a1b2c3d HEAD@{0}: commit: feat(search): add autocomplete
e4f5g6h HEAD@{1}: checkout: moving from main to feat/add-search
i7j8k9l HEAD@{2}: commit: chore(deps): update vite
m0n1o2p HEAD@{3}: rebase (finish): returning to refs/heads/main
q3r4s5t HEAD@{4}: rebase (pick): fix(auth): handle timeout
u6v7w8x HEAD@{5}: rebase (start): checkout origin/main

Chaque ligne montre : le hash du commit, la référencé (HEAD@{N}), l'opération, et une description. C'est l'historique complet de tes deplacements dans le graphe git.

La différence avec git log : le log montre l'historique des commits de la branche courante. Le reflog montre l'historique de tes actions. Un commit supprime disparaît du log mais reste dans le reflog.

Scénario 1 : branche supprimee par erreur

Tu supprimes une branche par accident. Ca arrive. Surtout avec git branch -D qui force la suppression sans vérifier si les changements ont ete merges.

bashgit branch -D feat/search-v2
# Deleted branch feat/search-v2 (was a1b2c3d).

Pas de panique. Le commit est toujours la. Git ne supprime pas les objets immédiatement. Il faut juste retrouver le hash.

bashgit reflog
# ... cherche l'entree ou tu etais sur cette branche ...
# a1b2c3d HEAD@{7}: commit: feat(search): add fuzzy matching
bashgit checkout -b feat/search-v2 a1b2c3d

Ta branche est de retour, avec tous ses commits. Si tu ne trouves pas le hash dans le reflog (par exemple, tu n'as jamais checkout cette branche toi-meme), git te l'a affiche quand tu l'as supprimee : (was a1b2c3d). Regarde ton terminal.

Scénario 2 : rebase qui tourne mal

Tu fais un rebase et le résultat est catastrophique. Les conflits etaient trop complexes, tu as fait des erreurs de résolution, le code ne compile plus. Tu as deja fait git rebase --continue sur les 8 commits. Trop tard pour --abort.

bashgit reflog
# a1b2c3d HEAD@{0}: rebase (finish): returning to refs/heads/feat/search
# ... plein d'etapes de rebase ...
# m0n1o2p HEAD@{9}: rebase (start): checkout origin/main
# q3r4s5t HEAD@{10}: commit: feat(search): add results page

HEAD@{10} est l'état juste avant le rebase. Tu y retournes :

bashgit reset --hard HEAD@{10}

C'est comme si le rebase n'avait jamais eu lieu. Ta branche est revenue a son état d'avant. Tu peux recommencer plus calmement, ou choisir un merge a la place.

J'utilise ca au moins une fois par mois. Pas parce que je fais beaucoup d'erreurs (enfin, si, un peu), mais parce que parfois un rebase avec 15 conflits, c'est juste pas le bon moment. Tu reset, tu fais un merge classique, tu avances.

Scénario 3 : commit amende par erreur

Tu fais git commit --amend sur le mauvais commit. Ou tu amendes en ecrasant un message que tu voulais garder. L'ancien commit semble perdu puisque git log montre la version amendee.

Mais le reflog garde l'ancien :

bashgit reflog
# a1b2c3d HEAD@{0}: commit (amend): feat(search): add autocomplete (v2)
# e4f5g6h HEAD@{1}: commit: feat(search): add autocomplete

HEAD@{1} est le commit original, avant l'amend. Tu peux le restaurer :

bashgit reset --soft HEAD@{1}

--soft garde les modifications en staging. Tu peux re-commiter proprement.

Scénario 4 : le force push sur main

L'anecdote de l'introduction. Un dev force push sur main et ecrase des commits. Sur le serveur GitLab, ces commits sont partis (le reflog serveur est généralement inaccessible ou désactivé). Mais sur ta machine locale, ton reflog garde les références.

bashgit reflog main
# a1b2c3d main@{0}: reset: moving to HEAD~5  <-- le force push a fait ca
# e4f5g6h main@{1}: pull: Fast-forward         <-- l'etat d'avant
bashgit checkout main
git reset --hard main@{1}
git push --force

Ca restaure main a son état d'avant le desastre. C'est exactement ce que j'ai fait pour sauver les trois jours de travail de l'équipe.

Depuis cet episode, j'ai ajoute deux protections :

  1. Branche main protégée sur GitLab (Settings > Repository > Protected branches) : push interdit, seul le merge via MR est autorise
  2. Regle receive.denyNonFastForwards sur le remote si tu geres ton propre serveur git

git fsck : les objets orphelins

Si le reflog ne suffit pas (par exemple, tu cherches un commit qui date de plus de 90 jours ou que le reflog a ete nettoye), git fsck peut trouver des objets orphelins :

bashgit fsck --lost-found
# dangling commit a1b2c3d4e5f6...
# dangling commit e4f5g6h7i8j9...
bashgit show a1b2c3d4e5f6

Ca te montre le contenu du commit orphelin. Si c'est celui que tu cherches, tu peux créer une branche dessus.

En pratique, j'ai utilise fsck une seule fois en cinq ans. Le reflog resout 99% des cas.

git gc et la fenêtre de 90 jours

Git fait du garbage collection periodiquement (git gc). Les objets non références depuis plus de 90 jours sont supprimes définitivement. Avant 90 jours, tout est recuperable.

bash# Voir la config d'expiration
git config gc.reflogExpire
# default: 90 days

Ne lance pas git gc --prune=now sauf si tu sais exactement ce que tu fais. Ca supprime immédiatement les objets non références. Sur paltemps.fr, j'ai un cron qui fait git gc hebdomadaire sur le serveur, mais avec les paramètres par défaut (90 jours).

La leçon

Git ne perd presque rien. Le reflog est ton filet de sécurité. La prochaine fois que tu paniques apres un reset, un rebase, ou un force push, respire et tape git reflog. La réponse est probablement dedans.

Mais le meilleur recovery, c'est celui dont tu n'as pas besoin. Protege tes branches, utilise --force-with-lease au lieu de --force, et impose des hooks qui attrapent les erreurs avant qu'elles arrivent.


Article précédent : 06 - Git hooks Article suivant : 08 - Glossaire

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.