Nutze die Kraft von 'git grep' für effiziente Codesuche

Wann 'git grep' besser ist als plain grep, wann 'rg' (ripgrep) beide schlägt, und was 'git grep' tatsächlich mit .gitignore macht (Spoiler: nichts).

In einem weiten Königreich voller zahlloser Schriftrollen und Manuskripte lebte ein Gelehrter namens Alaric. Seine Bibliothek war gewaltig - ein Labyrinth des Wissens, in dem alte Texte neben zeitgenössischen Schriften standen und Geheimnisse zwischen den Zeilen verborgen lagen. Alaric suchte oft nach einer einzigen schwer greifbaren Formulierung in diesem Meer aus Information, eine Aufgabe, die mit jedem Tag einschüchternder wurde.

Eines Morgens, als die Sonne goldene Strahlen auf die staubigen Folianten warf, machte Alaric sich daran, ein bestimmtes Konzept in seinen Archiven zu finden, das nur als "The Whispering Sigil" bekannt war. Er arbeitete sich durch Band um Band und nutzte seine üblichen Methoden, um die Seiten zu durchsieben - Methoden, die ihm inzwischen träge und ungenau vorkamen. Je tiefer er suchte, desto stärker verhedderte er sich in irrelevanten Passagen, Duplikaten und irreführenden Verweisen. Die Frustration wuchs, als aus Stunden Tage wurden und kaum Fortschritt zu sehen war.

Dann besuchte ein alter Weiser Alaric und bemerkte seine Not. Mit wissendem Lächeln sagte der Weise: "Vielleicht suchst du auf die mühsame Art. Es gibt einen verborgenen Pfad, den nur jene kennen, die ihr Wissen klug ordnen." Neugierig hörte Alaric zu, während der Weise ihm eine Methode erklärte, die seine Suche fokussierte, das Durcheinander durchtrennte und ihn direkt zu den Texten führte, die er suchte.

Mit diesem neuen Ansatz bewaffnet, versuchte Alaric es erneut. Diesmal verblasste der irrelevante Ballast. Der Weg zu "The Whispering Sigil" wurde klar, und er fand mit erstaunlicher Geschwindigkeit, wonach er gesucht hatte. Es war, als hätte er ein geheimes Tor in seinem Labyrinth aufgeschlossen, das ihm schnellen Zugriff auf genau das Wissen gab, das er brauchte.

Puff! Das Geheimnis war gelüftet: die Kraft von git grep.

Was git grep eigentlich ist

Plain grep -r läuft durch das Dateisystem. Es liest pflichtbewusst alles, was ihm in den Weg kommt: Quellcode, Logdateien, Build-Ausgaben, diese verirrte 4-MB-Dumpdatei, die dein Kollege vergessen hat zu löschen, den gesamten node_modules-Baum. git grep macht etwas Schmaleres: Es durchsucht die Dateien, die Git bereits kennt. Aus genau dieser einen Designentscheidung kommt der größte Teil seines Werts.

Worin git grep gut ist

  • Es durchsucht getrackte Dateien, nicht das Dateisystem. Git hält eine Liste jeder Datei, die du je gestaged oder committet hast - den Index. git grep liest aus dieser Liste. Ungetrackter Müll ist dort schlicht nicht vorhanden. Kein node_modules/, kein dist/, keine Coverage-Reports, keine zufällige Logdatei - weil Git nie von ihnen erfahren hat.

  • Es ist auf großen Repos schneller als grep -r. Die Dateiliste hat es bereits, also überspringt es den Lauf durchs Dateisystem. Es läuft mit mehreren Threads parallel. Der Gewinn ist real, aber keine Magie. git grep iteriert über dieselben Blobs wie grep, nur mit weniger Zeremonie. Es gibt keinen Content-Suchindex - der "Git index" ist eine Liste aus Dateipfaden und Blob-Hashes, kein invertierter Index im Lucene-Stil.

  • Es kann jede Ref ohne Checkout durchsuchen. Das ist das Killer-Feature. Ein Tag, ein Branch, ein Commit, ein Tree-Objekt - zeig mit git grep direkt darauf. Kein git checkout, kein Stash-Tanz, kein Umweg weg von dem, was du gerade getan hast.

Praktische Beispiele

Einfache Suche

Um in deinem Repository nach einem bestimmten Begriff wie "initializeSettings" zu suchen:

git grep "initializeSettings"

Das durchsucht alle getrackten Dateien im aktuellen Branch nach der exakten Übereinstimmung.

Suche ohne Beachtung der Groß-/Kleinschreibung

Für eine Suche ohne Beachtung der Groß-/Kleinschreibung, hilfreich, wenn du dir bei der Kapitalisierung unsicher bist:

git grep -i "initializesettings"

Das findet Treffer unabhängig von Unterschieden in der Groß-/Kleinschreibung.

Suche in einem bestimmten Branch

Um in einem anderen Branch zu suchen, ohne zu ihm zu wechseln, zum Beispiel in feature/login:

git grep "validateUser" feature/login

Das ist der Zug, der schwer zu schlagen ist. Kein Checkout, kein Stash, einfach die Antwort.

Suche über alle Branches hinweg

Um einen Begriff in jedem Branch zu suchen, einschließlich Remotes:

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

Um in jedem Commit zu suchen, von dem Git je gehört hat, nicht nur an der Spitze eines Branches:

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

Das findet Treffer in jedem Blob irgendwo in deiner History. In einem lebhaften Repo kann es einen Moment dauern - es läuft buchstäblich durch jeden Commit.

Suche in der Commit-History

Um herauszufinden, wann ein bestimmter String hinzugefügt oder entfernt wurde, nutze:

git log -S "optimizePerformance"

Das zeigt Commits, die den Begriff "optimizePerformance" eingeführt oder entfernt haben.

Um die tatsächlichen Diffs zu sehen, in denen der Begriff hinzugefügt oder entfernt wurde:

git log -G "optimizePerformance" -p

Reguläre Ausdrücke verwenden

git grep unterstützt reguläre Ausdrücke für fortgeschrittenere Suchen:

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

Das matcht Python-Funktionsdefinitionen: def, Whitespace, ein Funktionsname, dann eine literale öffnende Klammer. (In Extended Regex ist \( eine literale Klammer, während ( eine Gruppe bedeuten würde; deshalb steht der Backslash dort.)

Was git grep liest und was nicht

git grep läuft durch den Index. Das war's. Es parst .gitignore nicht. Viele Leute, einschließlich einer früheren Version dieses Posts, behaupten, es tue das - und die Behauptung ist fast wahr, ungefähr so, wie "die Erde ist flach" fast wahr ist, wenn man immer nur auf einen Parkplatz schaut.

Die beiden Dinge decken sich nur, weil gitignored Dateien normalerweise auch ungetrackt sind. In dem Moment, in dem eine Datei sowohl gitignored als auch getrackt ist - jemand hat git add -f ausgeführt, oder die Datei wurde committet, bevor die Regel existierte -, durchsucht git grep sie fröhlich. rg tut das nicht.

Du kannst das in zwanzig Sekunden beweisen:

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

Die genaue Aussage lautet also: git grep durchsucht getrackte Dateien. Dadurch überspringt es zufällig das meiste von dem, was .gitignore ebenfalls überspringen würde, aber der Mechanismus ist ein anderer, und der Randfall ist wichtig - besonders dann, wenn du nach einem String suchst, der am Ende in einer generierten Datei lebt, die vor Jahren jemand mit Gewalt hinzugefügt hat.

Der .gitignore-Mechanismus kommt nur über zwei Opt-in-Modi in git grep hinein:

  • --untracked - auch ungetrackte Dateien durchsuchen. In diesem Modus respektiert git grep standardmäßig .gitignore und überspringt ignorierte Dateien (mit --no-exclude-standard kannst du das überschreiben und sie ebenfalls durchsuchen).
  • --no-index - das aktuelle Verzeichnis durchsuchen und Git vollständig ignorieren. Nützlich innerhalb eines Repos, wenn du plain-grep-Semantik willst. In diesem Modus konsultiert git grep .gitignore standardmäßig nicht - nutze --exclude-standard, wenn du das möchtest.

Standard-git grep, ohne Flags, öffnet deine .gitignore-Datei nie.

Wann du stattdessen zu rg greifen solltest

git grep und rg (ripgrep) sind eigentlich keine Konkurrenten. Sie laufen durch unterschiedliche Dinge, und eine ernsthafte Werkzeugkiste hat beide.

  • git grep läuft durch den Index: getrackte Dateien, plus jede Ref oder jedes Tree-Objekt, auf das du zeigst.
  • rg läuft durch das Dateisystem: jede Datei unter dem aktuellen Verzeichnis, abzüglich dessen, was deine .gitignore, .ignore, .rgignore und globalen Excludes ihm zu überspringen sagen.

Jedes der beiden kann etwas, was das andere nicht kann.

git grep gewinnt, wenn du über die History hinweg suchen willst, ohne einen Checkout zu machen:

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 gewinnt, wenn du tatsächlich Dateisystem-Semantik mit korrekter gitignore-Behandlung willst - einschließlich eines frisch geklonten Unterordners, den du noch nicht mit git add aufgenommen hast, generierter Dateien, von denen Git nie gehört hat, oder eines Verzeichnisses, das gar kein Git-Repo ist:

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

rg ist auch die Engine hinter der Projektsuche von VS Code, weshalb sich "Find in Files" exakt so anfühlt wie rg im Terminal. Es hat solide Unicode-Unterstützung, und auf den meisten modernen Corpora ist es mindestens so schnell wie git grep, oft schneller - der Linux-Kernel-Benchmark im ripgrep README zeigt ripgrep bei derselben Query ungefähr 3x schneller als git grep -P. (Tipp: Wenn du das Verhalten "case-sensitive nur dann, wenn dein Pattern Großbuchstaben enthält" willst, übergib -S für smart-case - es ist Opt-in, nicht der Default.)

Falls du rg noch nicht installiert hast, behebe das:

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

Leg rg neben git grep in deine Werkzeugkiste. Sie decken unterschiedliche Jobs ab.

Vorteile von git grep

  • Relevanz. Es durchsucht nur, was du trackst. Build-Ausgaben, Caches und node_modules stehen dir nicht im Weg - weil Git sie nie gesehen hat.
  • Geschwindigkeit auf großen Repos. Multithreaded, kein Dateisystemlauf.
  • History-Reichweite. Jeder Branch, Tag oder Commit, ohne deinen Working Tree zu verlassen. Das ist der Teil, den rg nicht kann.
  • Weniger Binärrauschen. Wie grep markiert git grep Binärdateien mit "Binary file X matches", statt Bytes auszuspucken - aber weil es durch getrackte Dateien läuft, begegnet es normalerweise von Anfang an weniger davon. Übergib -I, um Binärdateien vollständig zu überspringen.

Zusätzliche Tipps

  • Ergebnisse paginieren:

    git grep "searchTerm" | less
  • Treffer pro Datei zählen:

    git grep -c "searchTerm"
  • Zeilennummern anzeigen:

    git grep -n "searchTerm"
  • Jeden Treffer im Editor öffnen:

    git grep -l "searchTerm" | xargs code

    Tausche code gegen nvim, subl oder was auch immer du benutzt.

Fazit

So wie Alaric in seiner labyrinthischen Bibliothek einen verborgenen Pfad fand, zieht git grep eine saubere Linie durch eine getrackte Codebase: schnell, branch-bewusst und frei von allem, wovon Git nie erfahren hat. Es ist kein universeller Ersatz für grep, und es ist kein Ersatz für rg. Es ist das Werkzeug, das den Index deines Repos kennt, und sobald du danach greifst, wird das Labyrinth deutlich kleiner.

Nutze git grep, wenn die Frage lautet: "wo in dieser Codebase, einschließlich ihrer History". Nutze rg, wenn die Frage lautet: "wo auf der Platte, unter Beachtung meiner Ignore-Regeln". An den meisten Tagen wirst du beide in Reichweite haben wollen.


Aktualisiert am 2026-04-27: korrigierte eine frühere Behauptung, dass git grep .gitignore respektiert (tut es nicht direkt), entschärfte die Erklärung zu "internem Indexing", korrigierte ein Regex-Beispiel und fügte einen Abschnitt dazu hinzu, wann stattdessen rg zu verwenden ist.


Kommentare

Boris D. Teoharov

Autor

Hallo, ich bin Boris

Ich bin kein Schriftsteller. Ich bin kein Philosoph. Ich bin nur ein Backend-Engineer aus Bulgarien, irgendwo zwischen Laravel-Queues und Indizes mit Hunderten Millionen Zeilen.