Artisan Numérique

/développement/gestionnaire de version/sgv/git/ Retrouver du code perdu avec GIT

Un jour on implémente une fonctionnalité. Quelques mois plus tard, on la retire. Et quelques mois plus tard encore on se dit, "Mais zut, je sais que j'avais fait cela, mais comment ?". C'est à ce stade que super-GIT vient à la rescousse, sous réserve d'avoir le script qui va bien.

En vrai, pour ceux qui connaissent, j'avais créé cette fonctionnalité pour chercher dans les révisions d'un module Drupal, field_group pour ne pas le nommer, à quel moment ce dernier devenait dépendant du diabolique module CTools. Mais quelques heures plus tard, j'y ai trouvé un usage bien plus intéressant de récupération de code perdu.

Alors peut-être ce script existe déjà, ou est-ce une fonctionnalité enfouie dans l'un des innombrables modules constituant la constellation GIT. Toujours est-il qu'après avoir un peu cherché, j'en ai été rendu à créer ma propre routine. Le besoin étant non pas de chercher simplement dans les logs, car ne nous voilons pas la face, les logs sont rarement suffisamment explicites, mais bien dans le code source lié à chaque révision. Ce qui nous donne quelque chose comme cela :

#! /bin/bash

while read revision ; do
  echo $revision;
  hash=$(echo $revision | awk '{print $1}')
  git diff $hash~1..$hash | grep -E "$1" && break;
done < <(git rev-list --all --pretty=oneline | tac | tail -n +2)
Recherche dans l'historique de GIT

Tout le script repose sur deux commandes. Tout d'abord git rev-list qui va nous fournir l'ensemble des révisions dans l'ordre anté-chronologique. On spécifie bien que toutes les révisions sont désirées (--all) ainsi qu'un formatage sur une seule ligne (--pretty=oneline) sous la forme hash message de log. La commande tac permet de retourner l'ordre des lignes et ainsi d'obtenir les révisions dans l'ordre chronologique. Enfin tail -n +2 renvoie la liste privée de sa première ligne.

Ensuite il suffit d'extraire le hash de la révision avec awk, et de faire un git diff entre la révision précédente (d'où le fait de supprimer la première ligne) par la syntaxe hash~n et la révision en cours par la syntaxe ...

Il ne reste alors plus qu'à faire un grep en prenant l'expression régulière passée en paramètre du script. Si Grep trouve, && chaine sur la commande break qui arrête la boucle. Ainsi si la chaine a été trouvée, cela nous donnera quelque chose comme cela :

gastongit-locate ma_fonction_perdue
f90f74d8545c6cd0d3a77cef22e7f79d9bdb241c bon format d'image en article
296d541aeccf6bd36123a441924aa5bd37210d14 correction blocks non-home
+function ma_fonction_perdue() {
exemple d'exécution

Je n'aurais alors plus qu'à faire un git diff entre les deux révisions, pipé sur un less pour chercher ma fonction et retrouver ainsi ce que j'avais perdu.

Il n'y a pas à dire, GIT est une fantastique boite à outils qui s'intègre à merveille au reste de l'écosystème GNU/*nix.