Comment grep (rechercher) du code engagé dans l'historique Git

1435

J'ai supprimé un fichier ou du code dans un fichier dans le passé. Puis-je grep dans le contenu (pas dans les messages de commit)?

Une très mauvaise solution consiste à grep le journal:

git log -p | grep <pattern>

Cependant, cela ne renvoie pas immédiatement le hachage de validation. J'ai joué git grepsans succès.

Ortwin Gentz
la source
2
Ces articles de blog de Junio ​​C Hamano (mainteneur de git) pourraient être intéressants pour vous: * L'outil de suivi de contenu ultime de Linus (à propos de la recherche de pioche git log -Set du blâme) * [Amusant avec "git log --grep"] [2] (recherche de messages de validation ) * [Amusant avec "git grep"] [3] [2]: gitster.livejournal.com/30195.html [3]: gitster.livejournal.com/27674.html
Jakub Narębski
la réponse d'un éventuel doublon fonctionne réellement: stackoverflow.com/a/1340245/492
CAD bloke
problème avec cela, c'est qu'il ne donne aucun contexte au changement .. c'est-à-dire qui / quand
Sonic Soul

Réponses:

1890

Pour rechercher du contenu de validation (c'est-à-dire des lignes de source réelles, par opposition aux messages de validation et autres), vous devez faire:

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

git rev-list --all | xargs git grep <expression> fonctionnera si vous rencontrez une erreur "Liste d'arguments trop longue".

Si vous voulez limiter la recherche à un sous-arbre (par exemple, "lib / util"), vous devrez le passer à la rev-listsous-commande et grepaussi:

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

Cela va parcourir tout votre texte de validation pour regexp.

La raison du passage du chemin dans les deux commandes est parce rev-listque renverra la liste des révisions où toutes les modifications se sont lib/utilproduites, mais vous devez également passer à grepafin qu'il ne recherche que lib/util.

Imaginez simplement le scénario suivant: greppourrait trouver la même chose <regexp>sur d'autres fichiers qui sont contenus dans la même révision retournée par rev-list(même s'il n'y a eu aucune modification de ce fichier sur cette révision).

Voici d'autres moyens utiles de rechercher votre source:

Rechercher dans l'arborescence de travail le texte correspondant à l'expression rationnelle expression régulière:

git grep <regexp>

Recherchez dans l'arborescence de travail des lignes de texte correspondant à l'expression régulière regexp1 ou regexp2:

git grep -e <regexp1> [--or] -e <regexp2>

Recherchez dans l'arborescence de travail les lignes de texte correspondant aux expressions régulières regexp1 et regexp2, en signalant uniquement les chemins d'accès aux fichiers:

git grep -l -e <regexp1> --and -e <regexp2>

Recherchez dans l'arborescence de travail des fichiers contenant des lignes de texte correspondant à l'expression régulière regexp1 et des lignes de texte correspondant à l'expression régulière regexp2:

git grep -l --all-match -e <regexp1> -e <regexp2>

Recherchez dans l'arbre de travail les lignes modifiées du modèle de correspondance de texte:

git diff --unified=0 | grep <pattern>

Rechercher toutes les révisions pour le texte correspondant à l'expression rationnelle expression régulière:

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

Rechercher toutes les révisions entre rev1 et rev2 pour le texte correspondant à l'expression régulière expression régulière:

git grep <regexp> $(git rev-list <rev1>..<rev2>)
Jeet
la source
61
Merci, fonctionne très bien! Il est triste cependant que "$ (git rev-list --all)" soit nécessaire et aucun commutateur pratique pour spécifier la recherche dans tout l'historique d'une branche.
Ortwin Gentz
3
Excellent. +1. Le GitBook ajoute quelques détails ( book.git-scm.com/4_finding_with_git_grep.html ), et Junio ​​C Hamano illustre certains de vos points: gitster.livejournal.com/27674.html
VonC
18
Malheureusement, je ne peux pas faire avancer cela avec msysgit-1.7.4. Ça me dit sh.exe": /bin/git: Bad file number. La réponse de VonC fonctionne également avec msysgit.
eckes
4
Si vous obtenez une erreur «impossible de lire l'arborescence» lorsque vous appelez l'historique git grep avec rev-list, vous devrez peut-être nettoyer les choses. Essayez git gcou consultez: stackoverflow.com/questions/1507463/…
Anthony Panozzo
8
Oui, cela semble également échouer sous Windows, hélas.
mlissner
552

Vous devez utiliser l' option pickaxe ( -S) de git log.

Pour rechercher Foo:

git log -SFoo -- path_containing_change
git log -SFoo --since=2009.1.1 --until=2010.1.1 -- path_containing_change

Voir l' historique Git - recherchez la ligne perdue par mot-clé pour en savoir plus.


Comme l'a commenté Jakub Narębski :

  • cela recherche les différences qui introduisent ou suppriment une instance de<string> . Cela signifie généralement "révisions où vous avez ajouté ou supprimé la ligne avec 'Foo'".

  • l' --pickaxe-regexoption vous permet d'utiliser l'expression régulière POSIX étendue au lieu de rechercher une chaîne. Exemple (de git log):git log -S"frotz\(nitfol" --pickaxe-regex


Comme Rob l'a commenté, cette recherche est sensible à la casse - il a ouvert une question de suivi sur la façon de rechercher sans tenir compte de la casse.

VonC
la source
3
Merci, je n'étais pas au courant de cette option. Il semble que ce soit la meilleure solution si vous êtes intéressé par les messages de validation et la solution de Jeet est la plus appropriée si vous avez besoin du comportement grep UNIX traditionnel de correspondance de ligne pure.
Ortwin Gentz
@Ortwin: d'accord (et j'ai voté pour la solution choisie). le git logbit dans votre question m'a confus;)
VonC
12
Combinez-le avec le -pdrapeau pour produire également le diff.
Sander
Existe-t-il un moyen d'exclure tous les répertoires correspondant à des modèles spécifiques en utilisant git log -S?
BakaKuna
3
@Anentropic, vous auriez besoin des --branches --alloptions pour rechercher le tout repo.
VonC
249

Ma façon préférée de le faire est avec git logl' -Goption de (ajoutée dans la version 1.7.4).

-G<regex>
       Look for differences whose added or removed line matches the given <regex>.

Il existe une différence subtile entre la façon dont les options -Get -Sdéterminent si une validation correspond:

  • L' -Soption compte essentiellement le nombre de fois que votre recherche correspond dans un fichier avant et après une validation. La validation est affichée dans le journal si les décomptes avant et après sont différents. Cela ne montrera pas, par exemple, les commits où une ligne correspondant à votre recherche a été déplacée.
  • Avec l' -Goption, la validation est affichée dans le journal si votre recherche correspond à une ligne ajoutée, supprimée ou modifiée.

Prenez ce commit comme exemple:

diff --git a/test b/test
index dddc242..60a8ba6 100644
--- a/test
+++ b/test
@@ -1 +1 @@
-hello hello
+hello goodbye hello

Étant donné que le nombre de fois où "bonjour" apparaît dans le fichier est le même avant et après cette validation, il ne correspondra pas à l'utilisation -Shello. Cependant, comme il y a eu un changement dans une correspondance de ligne hello, le commit sera affiché en utilisant-Ghello .

Tyler Holien
la source
2
Existe-t-il un moyen d'afficher le contexte de changement correspondant dans la sortie du journal git?
Thilo-Alexander Ginkel
13
@ Thilo-AlexanderGinkel - J'ajoute généralement l' -poption d'afficher un diff pour chaque commit. Ensuite, lorsque le journal est ouvert dans mon téléavertisseur, je recherche tout ce que je recherche. Si votre téléavertisseur est lesset vous git log -Ghello -p, vous pouvez taper /hello, appuyer Enteret utiliser net Npour rechercher les occurrences suivantes / précédentes de "bonjour".
Tyler Holien
J'ai trouvé un problème intéressant avec -GRegex: si la ligne de commande utilise UTF-8 et que le fichier que vous regardez utilise un codage ISO-latin (8 bits), .*échoue. Par exemple, j'ai un changement Vierter Entwurf-> Fünfter Entwurf, et tout en 'V.*ter Entwurf'produisant une correspondance, 'F.*ter Entwurf'non.
U. Windl
51

Si vous voulez parcourir les changements de code (voir ce qui a réellement été changé avec le mot donné dans toute l'histoire), optez pour le patchmode - j'ai trouvé une combinaison très utile de faire:

git log -p
# Hit '/' for search mode.
# Type in the word you are searching.
# If the first search is not relevant, hit 'n' for next (like in Vim ;) )
Bartek Skwira
la source
11
La solution acceptée ne fonctionne pas pour moi ni le git log -S. Celui-ci l'a fait!
rodvlopes
29

git log peut être un moyen plus efficace de rechercher du texte dans toutes les branches, en particulier s'il existe de nombreuses correspondances, et que vous souhaitez voir les modifications les plus récentes (pertinentes) en premier.

git log -p --all -S 'search string'
git log -p --all -G 'match regular expression'

Ces commandes de liste des commandes de journalisation ajoutent ou suppriment la chaîne de recherche / l'expression régulière donnée (généralement) la plus récente en premier. L' -poption permet d'afficher le diff correspondant à l'endroit où le modèle a été ajouté ou supprimé, afin que vous puissiez le voir dans son contexte.

Après avoir trouvé une validation pertinente qui ajoute le texte que vous recherchez (par exemple, 8beeff00d), recherchez les branches qui contiennent la validation:

git branch -a --contains 8beeff00d
Edward Anderson
la source
Salut, ces lignes ne semblent pas fonctionner du tout. Ma commande est> git log -p --all -S 'chaîne publique DOB {get; ensemble; } = string.Empty; ' et chaque fois que j'essaye de l'exécuter, j'obtiens> fatal: argument ambigu 'chaîne': révision ou chemin inconnu pas dans l'arbre de travail. > Utilisez '-' pour séparer les chemins des révisions, comme ceci:> 'git <command> [<revision> ...] - [<file> ...]'
user216652
@ user216652 Pour une raison quelconque, les 'guillemets ne regroupent pas votre chaîne de recherche en un seul argument. Au lieu de cela, 'publicl'argument est -Set traite le reste comme des arguments séparés. Je ne sais pas dans quel environnement vous exécutez, mais ce contexte serait nécessaire pour aider au dépannage. Je suggère d'ouvrir une question StackOverflow distincte si nécessaire pour vous aider à dépanner, avec tout le contexte de la façon dont votre commande git est envoyée au shell. Il me semble que ça se fait via une autre commande? Les commentaires ici ne sont pas le bon endroit pour comprendre cela.
Edward Anderson
26

J'ai pris la réponse de Jeet et l' ai adaptée à Windows (grâce à cette réponse ):

FOR /F %x IN ('"git rev-list --all"') DO @git grep <regex> %x > out.txt

Notez que pour moi, pour une raison quelconque, la validation réelle qui a supprimé cette expression régulière n'apparaissait pas dans la sortie de la commande, mais plutôt une validation avant celle-ci.

ripper234
la source
2
+1 - et si vous voulez éviter d'appuyer sur "q" après chaque recherche, ajoutez --no-pagerà la commande git à la fin
cgp
2
De plus, je voudrais noter que l'ajout à un fichier texte présente l'avantage supplémentaire d'afficher réellement le texte correspondant. (ajouter à un fichier texte en utilisant >>results.txtpour ceux qui ne connaissent pas la tuyauterie Windows ...
cgp
1
Et je pensais que la syntaxe de bash est moche :)
smido
23

Rechercher dans n'importe quelle révision, n'importe quel fichier :

git rev-list --all | xargs git grep <regexp>

Rechercher uniquement dans certains fichiers donnés, par exemple des fichiers XML:

git rev-list --all | xargs -I{} git grep <regexp> {} -- "*.xml"

Les lignes de résultat doivent ressembler à ceci: 6988bec26b1503d45eb0b2e8a4364afb87dde7af: bla.xml: texte de la ligne trouvée ...

Vous pouvez ensuite obtenir plus d'informations comme l'auteur, la date et la différence en utilisant git show:

git show 6988bec26b1503d45eb0b2e8a4364afb87dde7af
Christophe Roussy
la source
11

Pour plus de simplicité, je suggère d'utiliser GUI: gitk - Le navigateur du référentiel Git . C'est assez flexible

  1. Pour rechercher le code:

    Entrez la description de l'image ici
  2. Pour rechercher des fichiers:

    Entrez la description de l'image ici
  3. Bien sûr, il prend également en charge les expressions régulières:

    Entrez la description de l'image ici

Et vous pouvez parcourir les résultats à l'aide des flèches haut / bas.

watashiSHUN
la source
6

Pour toute autre personne essayant de le faire dans Sourcetree , il n'y a pas de commande directe dans l'interface utilisateur (à partir de la version 1.6.21.0). Cependant, vous pouvez utiliser les commandes spécifiées dans la réponse acceptée en ouvrant Terminal fenêtre (bouton disponible dans la barre d'outils principale) et en les copiant / collant.

Remarque: la vue Recherche de Sourcetree peut partiellement effectuer une recherche de texte pour vous. Appuyez sur Ctrl+ 3pour accéder à la vue Recherche (ou cliquez sur l'onglet Recherche disponible en bas). À l'extrême droite, définissez Type de recherche sur Modifications de fichier , puis tapez la chaîne que vous souhaitez rechercher. Cette méthode présente les limitations suivantes par rapport à la commande ci-dessus:

  1. Sourcetree ne montre que les commits qui contiennent le mot de recherche dans l' un des fichiers modifiés. La recherche du fichier exact qui contient le texte de recherche est à nouveau une tâche manuelle.
  2. RegEx n'est pas pris en charge.
point net
la source
4

Chaque fois que je me retrouve chez vous, j'utilise la ligne de commande suivante:

git log -S "<words/phrases i am trying to find>" --all --oneline  --graph

Explication:

  1. git log- Dois-je écrire plus ici; il montre les journaux par ordre chronologique.
  2. -S "<words/phrases i am trying to find>" - Il montre tous ces commits Git où n'importe quel fichier (ajouté / modifié / supprimé) a les mots / phrases que j'essaye de trouver sans les symboles '<>'.
  3. --all - Pour appliquer et rechercher dans toutes les branches.
  4. --oneline - Il comprime le journal Git sur une seule ligne.
  5. --graph - Il crée le graphique des validations chronologiquement ordonnées.
surajs1n
la source
1
"Chaque fois que je me retrouve chez toi, je ressens le besoin d'utiliser git!"
Sebi
1
C'est une excellente réponse!
Alf Eaton
@AlfEaton mon plaisir!
surajs1n
2

La réponse de Jeet fonctionne dans PowerShell.

git grep -n <regex> $(git rev-list --all)

Ce qui suit affiche tous les fichiers, dans n'importe quel commit, qui contiennent un fichier password.

# Store intermediate result
$result = git grep -n "password" $(git rev-list --all)

# Display unique file names
$result | select -unique { $_ -replace "(^.*?:)|(:.*)", "" }
Shaun Luttin
la source
1

Alors, essayez-vous de parcourir les anciennes versions du code pour voir où quelque chose existe en dernier?

Si je faisais cela, j'utiliserais probablement git bissect . En utilisant bisect, vous pouvez spécifier une bonne version connue, une mauvaise version connue et un script simple qui vérifie si la version est bonne ou mauvaise (dans ce cas, un grep pour voir si le code que vous recherchez est présent ). L'exécution de cette fonction détectera la date de suppression du code.

Rob Di Marco
la source
2
Oui, mais votre "test" peut être un script qui recherche le code et renvoie "vrai" si le code existe et "faux" si ce n'est pas le cas.
Rob Di Marco
2
Et si le code était mauvais dans la révision 10, devenait bon dans la révision 11 et redevenait mauvais dans la révision 15 ...
Paolo
2
Je suis d'accord avec Paolo. La recherche binaire n'est appropriée que pour les valeurs "ordonnées". Dans le cas de git bisect, cela signifie que toutes les "bonnes" révisions précèdent toutes les "mauvaises" révisions, à partir du point de référence, mais cette hypothèse ne peut pas être faite lors de la recherche d'un code transitoire. Cette solution peut fonctionner dans certains cas, mais ce n'est pas une bonne solution à usage général.
Kent
Je pense que c'est très inefficace car l'arbre entier est vérifié plusieurs fois pour la bissect.
U. Windl
0

Scénario: vous avez fait un grand nettoyage de votre code en utilisant votre IDE. Problème: l'EDI a nettoyé plus qu'il ne le devrait et maintenant votre code ne compile pas (ressources manquantes, etc.)

Solution:

git grep --cached "text_to_find"

Il trouvera le fichier où "text_to_find" a été modifié.

Vous pouvez maintenant annuler cette modification et compiler votre code.

Garytech
la source
0
git rev-list --all | xargs -n 5 git grep EXPRESSION

est un ajustement à la solution de Jeet , donc il montre les résultats pendant qu'il recherche et pas seulement à la fin (ce qui peut prendre beaucoup de temps dans un grand référentiel).

laktak
la source
-1

Dans mon cas, je devais rechercher un court commit et les solutions listées ne fonctionnaient malheureusement pas.

J'ai réussi à le faire avec (remplacer le token REGEX ):

for commit in $(git rev-list --all --abbrev-commit)
do
    if [[ $commit =~ __REGEX__ ]]; then 
        git --no-pager show -s --format='%h %an - %s' $commit
    fi
done
user9869932
la source