Git fsck, ça sauve des vies

Attention, cet article n'est pas destiné à des personnes débutantes en informatique et n'étant pas familière avec le gestionnaire de version Git. Pour plus d'info sur ce dernier, vous pouvez vous rendre sur son site officiel.

Aujourd'hui au travail, Git m'a sauvé la vie. Et je me devais de vous conter cette merveilleuse histoire.

Il était une fois un stagiaire...

Il était une fois un stagiaire qui venait de faire plusieurs commits sur une branche de développement en local. Ce stagiaire, tellement concentré, a fini son travail sur sa branche : le code est écrit et documenté (personne n'y croit ? bizarre...) et mieux encore, il fonctionne !

Il s'apprête à push son travail sur le repository distant pour ensuite faire un superbe Merge Request vers la branche... Release ! Et oui, c'est là qu'il s'aperçut que son travail était basé sur la branche master et non la branche release. Fichtre !

Heureusement, Git pense à vous et a inventé la commande git rebase. Cette commande magique vous permet de faire en sorte que votre branche (issue d'un parent) soit en fait issue d'un autre parent.

Le stagiaire a toujours une confiance en lui sans faille et se dit qu'il va tester l'option --onto de la commande git rebase. D'abord parce que si c'est bien fait, on arrive au même résultat qu'avec un

git rebase release

Et ensuite, parce que ça a trop la classe et ça fait kikoo. C'est un peu la version RoxXxoR du git rebase !

git rebase --onto le maléfique

Effectivement, git rebase --onto est de bon aloi quand on n'oublie pas certains paramètres. En fait, il attend trois paramètres :

  • la branche sur laquelle on doit se baser ;
  • le départ ;
  • l'arrivée.

Si vous oubliez le dernier paramètre, c'est comme oublier la bolognaise dans les spaguettis bolo : ça marche, mais c'est de la merde.

Le stagiaire, tout excité qu'il est de passer dans le monde des dieux de la commande git, tape donc :

git rebase --onto release branche_stagiaire

Le résultat ne se fait pas attendre : sa branche est bien rebase sur la branche release mais elle est au même niveau. Il n'y a aucun commit sur cette branche ! Au-cun ! Ils ont tous disparu ! Tout son travail acharné de stagiaire, tout ce code écrit entre deux services de cafés et de photocopies : envolé !

Rien dans les logs, rien dans les autres branches non plus (et particulièrement dans master qui était la branche parente avant). Bref, le stagiaire est dans la merde.

git fsck, you fools!

Bien évidemment, direction le grand devin Google pour trouver une solution auprès de l'Oracle (notez le jeu de mot). Là, on tombe sur un parchemin intéressant où une personne demande comment on peut retrouver des commits perdus.

Il est intéressant de noter que cette personne devait être stagiaire aussi, sauf qu'elle a fait pire action que notre protagoniste : même lui n'aurait jamais osé faire un git reset --HARD^... Paix à son âme.

Mais la solution proposée est à tester. Ni vu, ni connu, notre héros se lance ! Non sans avoir fait une copie du .git au cas où (on ne sait jamais).

La commande magique, qui apparaît telle le Gandalf de Git est git fsck. Si en plus vous l'équipez du paramètre --lost-found, vous avez un Gandalf avec un baton +5 en recherche de commits perdus.

Mais que fait donc cette commande mageek ? Elle retourne l'ensemble des objets (commits, trees ou blobs) présents dans la mémoire de Git mais qui ne sont plus référencés nul part.
Le stagiaire se retrouve donc ébahi devant tant de magie, et accessoirement avec ceci à l'écran :

commit 58af5483000015db...
tree 95ac6615530569...

Evidemment, il connaissait le hash de son commit par coeur... Bien sûr que non ! Il a donc dû faire un git show suivi de chaque hash présent dans la liste jusqu'à retrouver son commit détaillé !

Et là, c'est son commit, que dis-je, LE commit qui apparaît en face de ses yeux !

Un checkout et ça repart !

Maintenant que notre héros a le Saint-Graal à portée de main, il ne va pas le laisser filer. Immédiatement, un git checkout hashDuCommit et la création d'une branche directement après et c'est reparti !

Il a récupéré son travail, tout son travail. Le tout est dans une branche, sur laquelle il peut faire un... simple git rebase release du coup ! L'ensemble de ses collègues se paye sa tête (et oui, il est stagiaire), et le monde peut recommencer à tourner : la vie d'un stagiaire a été sauvée aujourd'hui.

Les explications

Revenons maintenant aux choses sérieuses. La magie, c'était quoi dans tout ça ? Et bien j'ai découvert aujourd'hui que Git garde en mémoire des objets (de type commit, blob ou tree) qui n'ont plus de références.

Il les garde en mémoire jusqu'à ce que le Garbage Collector (GC) passe et supprime tout ça !

La commande git fsck --lost-found permet d'afficher la liste de ces objets sans référence. Evidemment, cette liste est vide si le GC est passé d'ici à ce que vous utilisiez git fsck.

Le GC passe de deux façons, mais les deux nécessitent une action utilisateur. Dans la première, il est appelé automatiquement lors de certaines commande. Par exemple, un git fetch --all --prune vous supprime définitivement ces objets. De même qu'un git gc qui appelle le GC... (ok, je sors ; mais la commande existe !). La deuxième façon est qu'à chaque commande que vous tapez dans Git, le GC est appelé. Cependant, il regarde deux paramètres. Quand ils dépassent la valeur de ces paramètres, il effectuera son action, sinon il n'en fera rien.

Voilà, vous en savez un peu plus sur Git.
Donc n'oubliez pas : git fsck --lost-found, ça sauve des vies !


Sources

Mastodon