Git
Chapters ▾ 2nd Edition

7.5 Git Tools - Suchen

Suchen

Bei fast jeder Codebasis musst du oft herausfinden, wo eine Funktion aufgerufen oder definiert wird, oder die Historie einer Methode anzeigen. Git bietet eine Reihe nützlicher Werkzeuge, um den Code und die in seiner Datenbank gespeicherten Commits schnell und einfach zu durchsuchen. Im Folgenden gehen wir ein paar davon durch.

Git Grep

Git wird mit einem Befehl namens grep ausgeliefert, der es dir ermöglicht, auf einfache Weise einen beliebigen Verzeichnisbaum, das Arbeitsverzeichnis oder sogar die Staging-Area nach einer Zeichenkette (engl. string) oder einem regulären Ausdruck (engl. regular expression) zu durchsuchen. Für die folgenden Beispiele werden wir den Quellcode von Git selbst durchsuchen.

Standardmäßig durchsucht git grep die Dateien in deinem Arbeitsverzeichnis. Als erste Variante kannst du eine der Optionen -n oder --line-number verwenden, um die Zeilennummern anzuzeigen, bei denen Git Übereinstimmungen gefunden hat:

$ git grep -n gmtime_r
compat/gmtime.c:3:#undef gmtime_r
compat/gmtime.c:8:      return git_gmtime_r(timep, &result);
compat/gmtime.c:11:struct tm *git_gmtime_r(const time_t *timep, struct tm *result)
compat/gmtime.c:16:     ret = gmtime_r(timep, result);
compat/mingw.c:826:struct tm *gmtime_r(const time_t *timep, struct tm *result)
compat/mingw.h:206:struct tm *gmtime_r(const time_t *timep, struct tm *result);
date.c:482:             if (gmtime_r(&now, &now_tm))
date.c:545:             if (gmtime_r(&time, tm)) {
date.c:758:             /* gmtime_r() in match_digit() may have clobbered it */
git-compat-util.h:1138:struct tm *git_gmtime_r(const time_t *, struct tm *);
git-compat-util.h:1140:#define gmtime_r git_gmtime_r

Zusätzlich zur oben gezeigten einfachen Suche unterstützt git grep eine Vielzahl weiterer interessanter Optionen.

Anstatt beispielsweise alle Übereinstimmungen anzuzeigen, kannst du die Ausgabe von git grep mit der Option -c oder --count zusammenfassen. Git zeigt dir dann nur an, welche Dateien den Suchbegriff enthalten und wie viele Übereinstimmungen es in jeder Datei gibt:

$ git grep --count gmtime_r
compat/gmtime.c:4
compat/mingw.c:1
compat/mingw.h:1
date.c:3
git-compat-util.h:2

Wenn du dich für den Kontext eines Suchbegriffs interessieren, kannst du die umschließende Methode oder Funktion für jeden passenden Suchbegriff mit einer der Optionen -p oder --show-function anzeigen:

$ git grep -p gmtime_r *.c
date.c=static int match_multi_number(timestamp_t num, char c, const char *date,
date.c:         if (gmtime_r(&now, &now_tm))
date.c=static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
date.c:         if (gmtime_r(&time, tm)) {
date.c=int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset)
date.c:         /* gmtime_r() in match_digit() may have clobbered it */

Wie du sehen kannst, wird die Routine gmtime_r sowohl von den Funktionen match_multi_number als auch match_digit in der Datei date.c aufgerufen (die dritte angezeigte Übereinstimmung stellt nur den String dar, der in einem Kommentar erscheint).

Du kannst mit --and nach komplexen Kombinationen von Strings suchen, was sicherstellt, dass mehrere Übereinstimmungen in der gleichen Textzeile vorkommen müssen. Suchen wir zum Beispiel nach Zeilen, die eine Konstante definieren (den Teilstring #define enthalten), deren Name einen der Teilstrings LINK oder BUF_MAX enthält. Wir suchen hier in einer älteren Version der Git-Codebasis, die durch den Tag v1.8.0 repräsentiert wird (wir werden die Optionen --break und --heading hinzufügen, um die Ausgabe in ein besser lesbares Format aufzuteilen):

$ git grep --break --heading \
    -n -e '#define' --and \( -e LINK -e BUF_MAX \) v1.8.0
v1.8.0:builtin/index-pack.c
62:#define FLAG_LINK (1u<<20)

v1.8.0:cache.h
73:#define S_IFGITLINK  0160000
74:#define S_ISGITLINK(m)       (((m) & S_IFMT) == S_IFGITLINK)

v1.8.0:environment.c
54:#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS

v1.8.0:strbuf.c
326:#define STRBUF_MAXLINK (2*PATH_MAX)

v1.8.0:symlinks.c
53:#define FL_SYMLINK  (1 << 2)

v1.8.0:zlib.c
30:/* #define ZLIB_BUF_MAX ((uInt)-1) */
31:#define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */

Der Befehl git grep hat einige Vorteile gegenüber normalen Suchbefehlen wie grep und ack. Der erste Vorteil ist, dass es sehr schnell ist, der zweite, dass du jeden Baum in Git durchsuchen kannst, nicht nur das Arbeitsverzeichnis. Wie wir im obigen Beispiel gesehen haben, haben wir nach Begriffen in einer älteren Version des Git-Quellcodes gesucht, nicht in der Version, die gerade ausgecheckt war.

Stichwortsuche in Git Log

Vielleicht suchst du nicht, wo ein Begriff existiert, sondern wann er existiert oder eingeführt wurde. Der Befehl git log verfügt über eine Reihe leistungsfähiger Werkzeuge, um bestimmte Commits anhand des Inhalts ihrer Nachrichten, oder sogar anhand des Inhalts des von ihnen eingeführten Diffs zu finden.

Wenn wir zum Beispiel herausfinden wollen, wann die Konstante ZLIB_BUF_MAX ursprünglich eingeführt wurde, können wir die Option -S (umgangssprachlich als Git „pickaxe“ Option bezeichnet) verwenden, um Git anzuweisen, uns nur die Commits anzuzeigen, in denen die Anzahl der Vorkommen dieses Strings geändert wurde.

$ git log -S ZLIB_BUF_MAX --oneline
e01503b zlib: allow feeding more than 4GB in one go
ef49a7a zlib: zlib can only process 4GB at a time

Wenn wir uns den Unterschied dieser Commits ansehen, können wir sehen, dass die Konstante in ef49a7a eingeführt und in e01503b geändert wurde.

Wenn du spezifischer sein willst, kannst du mit der Option -G einen regulären Ausdruck für die Suche angeben.

Zeilen- und Funktionssuche in Git Log

Eine weitere ziemlich fortgeschrittene Logsuche, die wahnsinnig nützlich ist, ist die Suche nach dem Zeilen- und Funktionsverlauf. Führe einfach git log mit der Option -L aus, und es wird dir die Historie einer Funktion oder Codezeile in deiner Codebasis anzeigen.

Wenn wir zum Beispiel jede Änderung an der Funktion git_deflate_bound in der Datei zlib.c sehen wollten, könnten wir git log -L :git_deflate_bound:zlib.c ausführen. Dies wird versuchen, die Grenzen dieser Funktion herauszufinden und dann die Historie durchzusehen und uns jede Änderung, die an der Funktion vorgenommen wurde, als eine Reihe von Patches bis zum Zeitpunkt der ersten Erstellung der Funktion zu zeigen.

$ git log -L :git_deflate_bound:zlib.c
commit ef49a7a0126d64359c974b4b3b71d7ad42ee3bca
Author: Junio C Hamano <gitster@pobox.com>
Date:   Fri Jun 10 11:52:15 2011 -0700

    zlib: zlib can only process 4GB at a time

diff --git a/zlib.c b/zlib.c
--- a/zlib.c
+++ b/zlib.c
@@ -85,5 +130,5 @@
-unsigned long git_deflate_bound(z_streamp strm, unsigned long size)
+unsigned long git_deflate_bound(git_zstream *strm, unsigned long size)
 {
-       return deflateBound(strm, size);
+       return deflateBound(&strm->z, size);
 }


commit 225a6f1068f71723a910e8565db4e252b3ca21fa
Author: Junio C Hamano <gitster@pobox.com>
Date:   Fri Jun 10 11:18:17 2011 -0700

    zlib: wrap deflateBound() too

diff --git a/zlib.c b/zlib.c
--- a/zlib.c
+++ b/zlib.c
@@ -81,0 +85,5 @@
+unsigned long git_deflate_bound(z_streamp strm, unsigned long size)
+{
+       return deflateBound(strm, size);
+}
+

Wenn Git nicht herausfinden kann, wie man eine Funktion oder Methode in deiner Programmiersprache abgleicht, kannst du Git auch einen regulären Ausdruck (engl. regular expression oder regex) mitgeben. Zum Beispiel hätte Folgendes das Gleiche getan wie das obige Beispiel: git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c. Du kannst Git auch einen Bereich von Zeilen oder eine einzelne Zeilennummer angeben und du erhältst die gleiche Art von Ausgabe.

scroll-to-top