Déverrouiller la puissance de 'git grep' pour rechercher efficacement dans le code

Quand 'git grep' bat grep tout court, quand 'rg' (ripgrep) les bat tous les deux, et ce que 'git grep' fait vraiment avec .gitignore (spoiler : rien).

Dans un vaste royaume rempli d’innombrables rouleaux et manuscrits vivait un érudit nommé Alaric. Sa bibliothèque était immense : un labyrinthe de savoir où les textes anciens se mêlaient aux écrits contemporains, et où les secrets se cachaient entre les lignes. Alaric se retrouvait souvent à chercher une seule phrase insaisissable au milieu de cette mer d’informations, une tâche qui devenait plus intimidante de jour en jour.

Un matin, tandis que le soleil projetait ses rayons dorés sur les tomes poussiéreux, Alaric partit retrouver un concept particulier mentionné dans ses archives, connu seulement sous le nom de « The Whispering Sigil ». Il parcourut des volumes entiers, utilisant ses méthodes habituelles pour passer les pages au crible — des méthodes qui semblaient désormais lentes et imprécises. Plus il avançait, plus il s’empêtrait dans des passages sans rapport, des doublons et des références trompeuses. La frustration monta tandis que les heures devenaient des jours, avec peu de progrès.

Puis un vieux sage rendit visite à Alaric et remarqua sa détresse. Avec un sourire entendu, le sage dit : « Peut-être cherches-tu par le chemin difficile. Il existe une voie cachée, connue seulement de ceux qui organisent leur savoir avec soin. » Intrigué, Alaric écouta le sage lui expliquer une méthode qui resserrait sa recherche, traversait le désordre et menait directement aux textes qu’il cherchait.

Armé de cette nouvelle approche, Alaric essaya de nouveau. Cette fois, le bruit inutile s’effaça. Le chemin vers « The Whispering Sigil » devint clair, et il trouva ce qu’il cherchait avec une rapidité stupéfiante. C’était comme s’il avait déverrouillé une porte secrète dans son labyrinthe, lui donnant un accès immédiat au savoir exact dont il avait besoin.

Pouf ! Le secret était révélé : la puissance de git grep.

Ce qu’est réellement git grep

Un simple grep -r parcourt le système de fichiers. Il lit consciencieusement tout ce qui se trouve sur son chemin : code source, fichiers de log, sorties de build, ce fichier dump perdu de 4 Mo que votre collègue a oublié de supprimer, l’arbre node_modules tout entier. git grep fait quelque chose de plus étroit : il cherche dans les fichiers que Git connaît déjà. C’est de ce choix de conception que vient l’essentiel de sa valeur.

Là où git grep est bon

  • Il cherche dans les fichiers suivis, pas dans le système de fichiers. Git garde une liste de chaque fichier que vous avez déjà staged ou commit — l’index. git grep lit depuis cette liste. Les déchets non suivis n’y sont tout simplement pas. Pas de node_modules/, pas de dist/, pas de rapports de couverture, pas de fichier de log aléatoire — parce que Git n’a jamais été informé de leur existence.

  • Il est plus rapide que grep -r sur les gros dépôts. Il possède déjà la liste des fichiers, donc il évite de parcourir le système de fichiers. Il lance plusieurs threads en parallèle. Le gain est réel, mais ce n’est pas de la magie. git grep itère sur les mêmes blobs que grep, simplement avec moins de cérémonie. Il n’y a pas d’index de recherche de contenu impliqué — « l’index Git » est une liste de chemins de fichiers et de hashes de blobs, pas un index inversé à la Lucene.

  • Il peut chercher dans n’importe quelle ref sans checkout. C’est la fonctionnalité qui tue. Un tag, une branche, un commit, un objet tree : pointez git grep directement dessus. Pas de git checkout, pas de danse autour du stash, pas de détour loin de ce que vous étiez en train de faire.

Exemples pratiques

Recherche de base

Pour chercher un terme précis, comme "initializeSettings", dans votre dépôt :

git grep "initializeSettings"

Cela scanne tous les fichiers suivis dans la branche courante pour trouver la correspondance exacte.

Recherche insensible à la casse

Pour une recherche insensible à la casse, utile quand vous n’êtes pas sûr de la capitalisation :

git grep -i "initializesettings"

Cela trouvera les correspondances quelles que soient les différences de casse.

Chercher dans une branche précise

Pour chercher dans une autre branche sans basculer dessus, par exemple dans feature/login :

git grep "validateUser" feature/login

C’est le geste difficile à battre. Pas de checkout, pas de stash, juste la réponse.

Chercher dans toutes les branches

Pour chercher un terme dans toutes les branches, y compris les remotes :

git branch -a | xargs -n 1 git grep "configureDatabase"

Pour chercher dans chaque commit dont Git a entendu parler, pas seulement à la pointe des branches :

git grep "configureDatabase" $(git rev-list --all)

Cela trouve les correspondances dans n’importe quel blob, n’importe où dans votre historique. Sur un dépôt actif, cela peut prendre un moment : il parcourt littéralement chaque commit.

Chercher dans l’historique des commits

Pour trouver quand une chaîne particulière a été ajoutée ou supprimée, utilisez :

git log -S "optimizePerformance"

Cela affiche les commits qui ont introduit ou supprimé le terme "optimizePerformance".

Pour voir les diffs réels où le terme a été ajouté ou supprimé :

git log -G "optimizePerformance" -p

Utiliser des expressions régulières

git grep prend en charge les expressions régulières pour des recherches plus avancées :

git grep -E "def\s+\w+\("

Cela correspond aux définitions de fonctions Python : def, un espace blanc, un nom de fonction, puis une parenthèse ouvrante littérale. (En regex étendue, \( est une parenthèse littérale et ( signifierait un groupe, d’où la barre oblique inverse.)

Ce que git grep lit, et ne lit pas

git grep parcourt l’index. C’est tout. Il ne parse pas .gitignore. Beaucoup de gens, y compris une version précédente de cet article, affirment qu’il le fait — et l’affirmation est presque vraie, de la même manière que « la Terre est plate » est presque vraie si vous ne regardez jamais qu’un parking.

Les deux ne s’alignent que parce que les fichiers gitignorés sont généralement aussi non suivis. Dès qu’un fichier est à la fois gitignoré et suivi — quelqu’un a lancé git add -f, ou le fichier a été commité avant que la règle existe — git grep le cherchera volontiers. rg, lui, ne le fera pas.

Vous pouvez le prouver en vingt secondes :

mkdir demo && cd demo
git init -q
echo "*.log" > .gitignore
echo "the secret phrase" > tracked.log
git add -f tracked.log .gitignore
git commit -qm init

git grep "secret phrase"   # finds it - the file is tracked, ignore rule notwithstanding
rg "secret phrase"         # finds nothing - rg actually reads .gitignore

La formulation précise est donc : git grep cherche dans les fichiers suivis. Cela saute par hasard la plupart de ce que .gitignore sauterait, mais le mécanisme est différent et le cas limite compte — surtout quand vous traquez une chaîne qui finit par vivre dans un fichier généré que quelqu’un a ajouté de force il y a des années.

Le mécanisme .gitignore n’entre dans git grep que par deux modes explicites :

  • --untracked — cherche aussi dans les fichiers non suivis. Dans ce mode, git grep respecte .gitignore par défaut et saute les fichiers ignorés (utilisez --no-exclude-standard pour les chercher eux aussi).
  • --no-index — cherche dans le répertoire courant en ignorant Git entièrement. Utile dans un dépôt quand vous voulez la sémantique de grep tout court. Dans ce mode, git grep ne consulte pas .gitignore par défaut — activez --exclude-standard si vous le voulez.

Le git grep par défaut, sans flags, n’ouvre jamais votre fichier .gitignore.

Quand choisir plutôt rg

git grep et rg (ripgrep) ne sont pas vraiment des concurrents. Ils parcourent des choses différentes, et une boîte à outils sérieuse contient les deux.

  • git grep parcourt l’index : les fichiers suivis, plus toute ref ou tout objet tree que vous lui indiquez.
  • rg parcourt le système de fichiers : chaque fichier sous le répertoire courant, moins ce que votre .gitignore, .ignore, .rgignore et vos exclusions globales lui disent de sauter.

Chacun fait quelque chose que l’autre ne peut pas faire.

git grep gagne quand vous voulez chercher dans l’historique sans checkout :

git grep "deprecated_api" v2.3.0          # search a tag
git grep "deprecated_api" HEAD~50         # 50 commits ago
git grep "deprecated_api" $(git rev-list --all)   # every commit, ever

rg gagne quand vous voulez réellement la sémantique du système de fichiers avec une bonne gestion de gitignore — y compris un sous-dossier fraîchement cloné que vous n’avez pas encore git add, des fichiers générés dont Git n’a jamais entendu parler, ou un répertoire qui n’est pas un dépôt Git du tout :

rg "deprecated_api"                # respects .gitignore by default
rg --no-ignore "deprecated_api"    # opt back into ignored files
rg --hidden "deprecated_api"       # include dotfiles

rg est aussi le moteur derrière la recherche de projet de VS Code, ce qui explique pourquoi « Find in Files » ressemble exactement à un rg lancé dans un terminal. Il gère solidement Unicode, et sur la plupart des corpus modernes, il est au moins aussi rapide que git grep, souvent plus rapide — le benchmark du noyau Linux dans le README de ripgrep montre ripgrep battant git grep -P d’environ 3x sur la même requête. (Astuce : si vous voulez le comportement « sensible à la casse seulement quand le motif contient une majuscule », passez -S pour smart-case — c’est opt-in, pas le défaut.)

Si vous n’avez pas encore installé rg, corrigez ça :

brew install ripgrep   # macOS
apt install ripgrep    # Debian/Ubuntu
cargo install ripgrep  # anywhere with Rust

Mettez rg à côté de git grep dans votre boîte à outils. Ils couvrent des tâches différentes.

Avantages de git grep

  • Pertinence. Il cherche seulement dans ce que vous suivez. Les sorties de build, caches et node_modules ne sont pas sur votre chemin — parce que Git ne les a jamais vus.
  • Vitesse sur les gros dépôts. Multi-threadé, sans parcours du système de fichiers.
  • Portée historique. Toute branche, tout tag ou tout commit, sans quitter votre working tree. C’est la partie que rg ne peut pas faire.
  • Moins de bruit binaire. Comme grep, git grep signale les binaires avec « Binary file X matches » au lieu de déverser des octets — mais comme il parcourt les fichiers suivis, il en rencontre généralement moins dès le départ. Passez -I pour ignorer entièrement les binaires.

Conseils supplémentaires

  • Paginer les résultats :

    git grep "searchTerm" | less
  • Compter les correspondances par fichier :

    git grep -c "searchTerm"
  • Afficher les numéros de ligne :

    git grep -n "searchTerm"
  • Ouvrir chaque correspondance dans votre éditeur :

    git grep -l "searchTerm" | xargs code

    Remplacez code par nvim, subl, ou ce que vous utilisez.

Conclusion

Tout comme Alaric a trouvé un chemin caché dans sa bibliothèque labyrinthique, git grep trace une ligne nette à travers une base de code suivie : rapide, conscient des branches, et dégagé de tout ce dont Git n’a jamais entendu parler. Ce n’est pas un remplacement universel de grep, et ce n’est pas un remplacement de rg. C’est l’outil qui connaît l’index de votre dépôt, et une fois que vous l’atteignez, le labyrinthe devient beaucoup plus petit.

Utilisez git grep quand la question est « où dans cette base de code, y compris dans son historique ». Utilisez rg quand la question est « où sur le disque, en respectant mes règles d’ignore ». La plupart des jours, vous voudrez les deux à portée de main.


Mis à jour le 2026-04-27 : correction d’une affirmation précédente selon laquelle git grep respecte .gitignore (ce n’est pas le cas directement), assouplissement de l’explication sur « l’indexation interne », correction d’un exemple de regex, et ajout d’une section sur le moment où utiliser plutôt rg.


Commentaires

Boris D. Teoharov

Auteur

Bonjour, je suis Boris

Je ne suis pas écrivain. Je ne suis pas philosophe. Je suis simplement un ingénieur backend bulgare, quelque part entre des files Laravel et des index de centaines de millions de lignes.