Retrouver du code perdu avec GIT
Le 31 mai, 2012 - 23:19 | Ulhume

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 :

gaston$git-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.

Vos remarques et commentaires...

Benoît, le 1 juin, 2012 - 09:30

Bonjour,

Git et mercurial, que je connais mieux, ont une commande grep.

hg grep

cherche directement dans toutes les révisions

Pour git c'est un peu plus compliqué, j'ai trouvé cette syntaxe :

git grep <regexp> $(git rev-list --all)

Voir http://stackoverflow.com/a/2929502/1037418

Benoît

Ulhume, le 2 juin, 2012 - 10:54

Alors oui tu as raison sur le principe mais dans les faits git-grep recherche dans tous les fichier de l'arborescence pour une révision donnée. Du coup c'est super long lorsqu'on a beaucoup de commit.

sleibo, le 1 juin, 2012 - 10:47

Bonjour,

On peut faire encore plus simple avec git :

git log -S<chaîne>
git log -G<regexp>

qui renvoie les commits où la chaîne de caractères (ou le motif correspondant à la regexp) a été introduite ou supprimée.

En ajoutant l'option -p on obtient aussi le patch correspondant.

Ulhume, le 2 juin, 2012 - 10:59

Ah bé oui là c'est bien mieux que ma méthode, merci :) En plus c'est redoutablement plus rapide. En revanche ma version de GIT (1.7.2.5) ne semble pas disposer de l'option -G. Tu utilises laquelle ?

sleibo, le 2 juin, 2012 - 15:23

J'utilise la version 1.7.10 (dispo sur debian dans squeeze-backports) mais la fonctionnalité a été intrdouite dans un commit datant du 31/8/2010 (merci git log -S ;) )

Ulhume, le 2 juin, 2012 - 21:01

Zarbidouille cette histoire, moi j'ai droit à un vilain unreconized argument -G. Ceci dit la version -S est déjà pas mal efficace.

sleibo, le 3 juin, 2012 - 19:51

Si tu connais un moyen pour récupérer le tag dans lequel apparaît une modification introduite dans un commit je suis preneur ; ça résoudrait notre petite interrogation !

sleibo, le 6 juin, 2012 - 16:21

Pour répondre à ma question :

git tag --contains <commit>

Ce qui me permet de te dire que la première version de git implémentant git log -G est la version 1.7.4

Ulhume, le 7 juin, 2012 - 07:46

Ah bé merci, je le note :) En tout cas logique donc que je n'ai pas le -G, je verrais à utiliser la backport si j'ai un jour besoin de regex.

Publier un nouveau commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.
  • To highlight piece of code, just surround them with <code type="language"> Your code &tl;/code>>. Language can be java,c++,bash,etc... Everything Geshi support.
  • Tags HTML autorisés : <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote> <div> <p> <br>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Les adresses de pages web et de courriels sont transformées en liens automatiquement.

Plus d'informations sur les options de formatage

CAPTCHA
Cette question est là pour déterminer si vous êtes humain ou pas...