Chapters ▾ 2nd Edition

7.5 Ferramentas do Git - Pesquisando (Searching)

Pesquisando (Searching)

Com uma base de código de quase qualquer tamanho, você frequentemente precisará encontrar onde uma função é chamada ou definida, ou exibir o histórico de um método. O Git fornece algumas ferramentas úteis para procurar através do código e dos commits armazenados no seu banco de dados de forma rápida e fácil. Analisaremos algumas delas.

Git Grep

O Git vem com um comando chamado grep que permite que você pesquise facilmente através de qualquer árvore commitada, o diretório de trabalho, ou mesmo o index em busca de uma string ou expressão regular. Para os exemplos a seguir, pesquisaremos no próprio código fonte do Git.

Por padrão, o git grep examinará os arquivos no seu diretório de trabalho. Como uma primeira variação, você pode usar as opções -n ou --line-number para imprimir os números das linhas onde o Git encontrou correspondências (matches):

$ 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

Além da pesquisa básica mostrada acima, git grep suporta uma infinidade de outras opções interessantes.

Por exemplo, em vez de imprimir todas as correspondências, você pode pedir ao git grep para resumir a saída, mostrando apenas quais arquivos continham a string de pesquisa e quantas correspondências havia em cada arquivo com a opção -c ou --count:

$ 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

Se você estiver interessado no contexto de uma string de pesquisa, poderá exibir o método ou função envolvente para cada string correspondente com as opções -p ou --show-function:

$ 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 */

Como você pode ver, a rotina gmtime_r é chamada tanto pela função match_multi_number quanto por match_digit no arquivo date.c (a terceira correspondência exibida representa apenas a string aparecendo em um comentário).

Você também pode pesquisar por combinações complexas de strings com a flag --and, que garante que múltiplas correspondências devem ocorrer na mesma linha de texto. Por exemplo, vamos procurar quaisquer linhas que definam uma constante cujo nome contenha qualquer uma das substrings “LINK” ou “BUF_MAX”, especificamente em uma versão mais antiga da base de código do Git representada pela tag v1.8.0 (incluiremos as opções --break e --heading que ajudam a dividir a saída em um formato mais legível):

$ 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 */

O comando git grep tem algumas vantagens em relação a comandos de pesquisa normais como grep e ack. A primeira é que ele é realmente rápido; a segunda é que você pode pesquisar em qualquer árvore no Git, e não apenas no diretório de trabalho. Como vimos no exemplo acima, procuramos por termos em uma versão mais antiga do código fonte do Git, e não na versão em que o checkout foi feito atualmente.

Pesquisa no Git Log

Talvez você não esteja procurando onde um termo existe, mas quando ele existiu ou foi introduzido. O comando git log possui uma série de ferramentas poderosas para encontrar commits específicos pelo conteúdo de suas mensagens ou mesmo pelo conteúdo do diff que eles introduzem.

Se, por exemplo, quisermos descobrir quando a constante ZLIB_BUF_MAX foi originalmente introduzida, podemos usar a opção -S (coloquialmente chamada de opção Git “pickaxe”) para dizer ao Git que nos mostre apenas os commits que alteraram o número de ocorrências dessa string.

$ 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

Se olharmos para o diff desses commits, podemos ver que em ef49a7a a constante foi introduzida e em e01503b ela foi modificada.

Se precisar ser mais específico, você pode fornecer uma expressão regular para pesquisar com a opção -G.

Outra pesquisa de log bastante avançada e absurdamente útil é a pesquisa de histórico de linha. Basta executar o git log com a opção -L, e ele mostrará o histórico de uma função ou linha de código na sua base de código.

Por exemplo, se quiséssemos ver todas as alterações feitas na função git_deflate_bound no arquivo zlib.c, poderíamos rodar git log -L :git_deflate_bound:zlib.c. Isso tentará descobrir quais são os limites dessa função e, em seguida, examinará o histórico e nos mostrará todas as alterações feitas na função como uma série de patches até o momento em que a função foi criada pela primeira vez.

$ 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);
+}
+

Se o Git não conseguir descobrir como combinar uma função ou método em sua linguagem de programação, você também pode fornecer a ele uma expressão regular (ou regex). Por exemplo, isso teria feito o mesmo que o exemplo acima: git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c. Você também poderia fornecer um intervalo de linhas ou um único número de linha e obteria o mesmo tipo de saída.