Chapitre 11 sur 13

Git reflog & fsck : historique local et commits perdus

Laisser un commentaire

Le reflog est un enregistrement local des références du projet, d’où son nom : reflog, le log des références. Il est local et n’est jamais pushé. Cet historique nous permet de voir les états successifs de toutes les références (les pointeurs) du repo. Tout est enregistré.

De son côté, fsck, qui signifie file system check, permet de vérifier l’intégrité du système de fichier de Git et d’identifier les commit n’ayant plus de parents.

Lorsqu’un commit est “effacé”, il est simplement déréférencé car il n’est plus atteignable par les commits de l’historique. Le reflog comme fsck permettent donc de récupérer des commits perdus.

reflog : visualiser l’historique local

La commande reflog permet d’exploiter le log du même nom. Nous avons fraichement cloné un repo (ici Git emojis).

# Le reflog est vide car nous venons juste de le cloner
git reflog
8aebae7 (HEAD -> master, origin/master, origin/HEAD) HEAD@{0}: clone: from git@github.com:Buzut/git-emojis-hook.git

On va maintenant “effacer” le dernier commit et observer de nouveau le reflog.

git reset HEAD~
git reflog
1f80958 (HEAD -> master) HEAD@{0}: reset: moving to HEAD~
8aebae7 (origin/master, origin/HEAD) HEAD@{1}: clone: from git@github.com:Buzut/git-emojis-hook.git

Le commit effacé n’est plus accessible par le log classique, mais le reflog la référence toujours. On peut donc se placer dessus avec switch ou checkout, voir en quoi il consiste avec show ou le réintégrer avec un reset. Plus impressionnant, le reflog nous permet même d’annuler un rebase.

Pour l’exemple, nous allons simplement enlever l’emoji de chacun des dix derniers commits avec git rebase -i HEAD~10. Cela a pour effet de modifier ces commits. Le reflog liste dans le détail toutes les actions effectuées.

git reflog
55d446d (HEAD -> master) HEAD@{0}: rebase -i (finish): returning to refs/heads/master
55d446d (HEAD -> master) HEAD@{1}: rebase -i (reword): update licence year
bf053e1 HEAD@{2}: rebase -i (reword): add exit 0 if no git dir for npm not to error
27ed7ee HEAD@{3}: rebase -i (reword): better exemple & explanations
15c50b1 HEAD@{4}: rebase -i (reword): fix typo
50f5a92 HEAD@{5}: rebase -i (reword): update docs
12ff12c HEAD@{6}: rebase -i (reword): add an exemple on how to automatically integrate to projects
6ba1b2c HEAD@{7}: rebase -i (reword): fix typo
62a2541 HEAD@{8}: rebase -i (reword): change icon for revert for a more expressive one
81b263e HEAD@{9}: rebase -i (reword): add a one-liner install method
2bbbbf4 HEAD@{10}: rebase -i (reword): avoid composed chars as they are problematic
0e7d828 HEAD@{11}: rebase -i: fast-forward
809be33 HEAD@{12}: rebase -i (start): checkout HEAD~10
1f80958 HEAD@{13}: reset: moving to HEAD~
8aebae7 (origin/master, origin/HEAD) HEAD@{14}: clone: from git@github.com:Buzut/git-emojis-hook.git

Un git status nous confirmera que la branche locale a totalement divergée par rapport à la branche distante. Il est cependant possible de replacer HEAD sur une référence de notre log qui se trouve avant l’altération de l’historique.

Vous avez peut-être remarqué que le reflog utilise la notation des références vue précédemment : HEAD@{<n>}. Le pointeur de HEAD a ici changé 14 fois depuis le clonage du dépôt. HEAD@{13} désigne l’état de HEAD juste avant le rebase et HEAD@{14} HEAD juste après le clonage.

À chaque fois, on a aussi le hash du dernier commit. HEAD pouvant aussi changer de branche, HEAD peut bouger dans que ce hash ne change.

git branch test && git switch test
git reflog
55d446d (HEAD -> test, master) HEAD@{0}: checkout: moving from master to test
55d446d (HEAD -> test, master) HEAD@{1}: rebase -i (finish): returning to refs/heads/master
55d446d (HEAD -> test, master) HEAD@{2}: rebase -i (reword): update licence year
bf053e1 HEAD@{3}: rebase -i (reword): add exit 0 if no git dir for npm not to error

On peut donc restaurer notre état initial avec git reset --hard HEAD@{15} (le changement de branche a ajouté une entrée dans l’historique). Un petit git status nous confirmera que tout est de nouveau en ordre.

Le reflog affiche par défaut le log de HEAD. Il est toutefois possible de lui demander n’importe quelle autre référence.

git reflog show <ref>

# Ou pour toutes les références
git reflog show --all

Enfin, sachez que par défaut, le reflog expire après 90 jours. Il est possible de personnaliser cette durée, elle est enregistrée dans l’option gc.reflogExpire. Vous pouvez également manuellement nettoyer le reflog avec git reflog expire.

fsck : lister et récupérer des commits perdus

Comme nous venons de le voir, le reflog est strictement local et sa durée de vie est limitée. Si nous recherchons des commits non référencés dans le projet et qui ne sont plus dans le reflog, nous avons une autre solution : fsck.

Cette commande permet de vérifier l’intégrité de la base de données et possède les options nécessaires pour retrouver nos références disparues. Elles peuvent être de trois types : blog, commit ou tree. Ce sont les types d’objets stockés par Git tel que nous l’avons vu dans le premier chapitre.

# Lister les commits non référencés
git fsck --unreachable

# Par défaut, les objets présents dans le reflog ne sont pas listés
# L'option --no-reflogs permet de les inclure
git fsck --unreachable --no-reflogs

# Filtrer la sortie pour n'afficher que les commits
git fsck --unreachable --no-reflogs | grep commit

# Pour directement visionner leurs contenus
git show --no-patch $(git fsck --unreachable --no-reflogs | grep commit | cut -d' ' -f3)

Bien que de nombreuses références soient récupérables de cette manière, les revs non référencées dans le projet finissent par être effacées par le ramasse miette. Pour en savoir plus à ce sujet, référez vous à la doc du garbage collector [en].

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```