@vouxique

Vou apresentar duas ferramentas de busca que mais pessoas deveriam conhecer, e que talvez possam ajudar no seu dia-a-dia, trabalhando com Git.

git log -S

Algo que você pode precisar fazer, por vezes, é descobrir em quais commits uma função, variável ou conceito foi introduzido, removido ou alterado.

O git log -S—carinhosamente chamado de “git pickaxe” devido à aparência da flag, que parece uma picareta—nos permite buscar nos diffs do nosso repositório por uma string que especificada. Isto é, ao invés de procurar nos arquivos, ele procura nas suas mudanças:

$ git log -S '+++'
commit a91d246f0a43fc2c33fd2aaef2f707c263f91b10
Author: Lucas Wolschick <snip>
Date:   Sat Oct 11 21:01:06 2025 -0300

    ajustes adicionais e novo post

commit 37d43c20e93319cd62990d9bba74dca6058ef74b
Author: Lucas W. <snip>
Date:   Wed Oct 8 22:25:00 2025 -0300

    blog post: resoluções

commit 9f201047ba32ae3a92c0deebced41e689a9152ac
Author: Lucas W. <snip>
Date:   Wed Oct 8 21:49:47 2025 -0300

    blog: Futuro

[...]

Se consultarmos um dos commits que a ferramenta retornou, vemos que, realmente, a string ‘+++’ foi introduzida nele:

$ git show a91d246f0a43fc2c33fd2aaef2f707c263f91b10
commit a91d246f0a43fc2c33fd2aaef2f707c263f91b10
Author: Lucas Wolschick <snip>
Date:   Sat Oct 11 21:01:06 2025 -0300

    ajustes adicionais e novo post

diff --git a/blog-src/content/posts/novo-tema-blog.md b/blog-src/content/posts/novo-tema-blog.md
new file mode 100644
index 0000000..db6d1f4
--- /dev/null
+++ b/blog-src/content/posts/novo-tema-blog.md
@@ -0,0 +1,11 @@
++++
+date = '2025-10-11T20:27:16-03:00'
+title = 'Novo tema'
+tags = ['blog']
++++
+
+Fiz algumas melhorias na navegabilidade do blog e refiz o layout dele...

[...]

Dito de outra forma, nas palavras do livro Pro Git, ele é útil quando “você não está procurando onde um termo existe, mas quando ele existe ou foi introduzido.” Você não está mais procurando no espaço, está procurando no tempo.

Um exemplo prático

Essa semana, no trabalho, eu estava refatorando uma classe e tive que remover um pouco de código antigo. O projeto do qual participo possui uma quality gate que requer 100% de cobertura dos testes para que as mudanças possam ser integradas na master. Após a refatoração (que teve vários commits), vi que a cobertura tinha diminuído para 99%.

Estranhado, fui ver no relatório de cobertura e descobri que as minhas mudanças fizeram com que um dos métodos do projeto deixasse de ser usado. Porém, eu não lembrava de ter visto esse método em lugar nenhum enquanto trabalhava. Para ver se eu tinha feito besteira e apagado algo que não deveria, fui lá e executei o git log -S:

git log -S 'MethodNameGoesHere' --oneline -p

Vendo o modo como ele era usado, pude confirmar que eu podia remover ele sem problemas.

💡 Dica: a flag -p imprime na tela o diff do commit, e a flag --oneline o mostra de maneira resumida—apenas o hash e primeira linha da mensagem. Outra flag útil é --reverse, para mostrar os commits mais velhos primeiro.

Outro exemplo! Suponha que queremos identificar os commits onde uma chamada de log foi introduzida ou removida:

git log -S '.Info(' --oneline -p

Pode ser útil se você está monitorando o uso de logging na sua aplicação.

Flags adicionais

Ao invés de -S, você pode usar -G caso queira usar uma expressão regular, ou -L para acompanhar como uma função evoluiu ao longo do tempo. Confira a documentação do git log --help para mais detalhes.

Experimente e me conte os resultados :-)

git bisect

Suponha que você esteja corrigindo um bug, mudança de comportamento indesejada ou regressão de desempenho. Suponha, além disso, que você não faz ideia do porquê o problema existe. Porém, você sabe que foi uma mudança no código-fonte que causou isso. Como você descobriria a causa raiz do problema de um modo eficiente?

O git bisect permite que você encontre commits que introduziram mudanças e/ou bugs de um jeito rápido e automatizável.

Como funciona? Você primeiro determina dois commits: um onde o problema não existia, e outro onde o problema passou a existir.

$ git bisect start
$ git bisect bad # commit atual tá quebrado
$ git bisect good master # mas o commit da master tá ok

A partir daí, o git bisect vai te levar numa aventura mágica pelo seu repositório, te fazendo perguntas estranhas, para no fim lhe revelar, como se numa epifania, onde o problema surgiu1.

Perdão?

As instruções são simples: o git bisect vai realizar alguns checkouts de commits específicos e mostrar eles para você. Após cada checkout, dê uma olhada no seu repositório e verifique se o problema existe. Se o commit estiver bom, você fala git bisect good. Se está ruim, você fala git bisect bad.

Um exemplo, se eu estivesse procurando por uma mudança na estilização do meu currículo:

$ git bisect start
status: waiting for both good and bad commits
$ git bisect good 9174a31
status: waiting for bad commit, 1 good commit known
$ git bisect bad
Bisecting: 8 revisions left to test after this (roughly 3 steps)
[1be4c73e3b26e32612033d7e7352a0be64a8fe25] Add files via upload
$ git bisect bad
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[1b8d82e79ef4771408e2bed2aa15f01d2deda101] adjustments
$ git bisect bad
Bisecting: 1 revision left to test after this (roughly 1 step)
[a15326b88f1cd38b7716c179359b101eb565b63f] open graph
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[248fc95bdb0513609652088e34c7c9735698ed8d] open graph 2

Quando não houver mais commits para responder, ele vai avisar:

$ git bisect good
1b8d82e79ef4771408e2bed2aa15f01d2deda101 is the first bad commit
commit 1b8d82e79ef4771408e2bed2aa15f01d2deda101
Author: Lucas Wolschick <snip>
Date:   Sun Sep 21 20:03:52 2025 -0300

    adjustments

 README.md   |  1 +
 cv/main.css | 15 +++++++++++++--
 index.html  |  4 ++--
 3 files changed, 16 insertions(+), 4 deletions(-)
 create mode 100644 README.md 

E eis que você descobriu qual é o commit que introduziu o bug.

Funcionamento do git bisect

Repare na primeira linha que ele exibiu após o ponto de partida e de fim serem especificados:

Bisecting: 8 revisions left to test after this (roughly 3 steps) 

Como ele consegue verificar 8 commits em apenas 3 passos?

O git bisect, internamente, executa uma busca binária sobre o histórico de commits do repositório dentro do intervalo especificado. Basicamente, ele corta o seu intervalo de commits no meio e pergunta: nesse ponto, está bom ou ruim?

  • Se estiver bom, então eu sei que o problema está na segunda metade do seu intervalo, e eu não preciso mais ver nada da primeira metade.
  • Se estiver ruim, então o problema aconteceu na primeira metade, e a segunda pode ser descartada.

Ou seja, a cada passo, ele joga metade dos problema restante fora2.

Puxando a sardinha aqui para a aula de análise de algoritmos lá da faculdade, a complexidade de tempo da busca binária é O(log2n)O(\log_2{n}), enquanto a da busca linear é O(n)O(n). O logaritmo aqui surge do resultado dessa relação recorrência:

T(n)=T(n2)+c T(n) = T\left(\frac{n}{2}\right) + c

Pelo teorema mestre, a forma fechada dessa relação é:

T(n)=clog2n+cO(log2n) T(n) = c\log_2{n}+c' \in O({\log_2{n}})

Sendo cc' o custo para resolver o caso base e cc o custo constante para resolver o caso recursivo.

Dito de outra forma, devido ao modo como ele funciona, ele conseguiria avaliar 10 000 commits em apenas 14 passos!

graph

Uma tabela comparativa mostrando o número de commits (coluna esquerda) e o número de passos (coluna direita) que o bisect teria de avaliar:

nn log2(n)\log_2{(n)}
1 0
10 ~3,32
100 ~6,64
1000 ~9,97
10000 ~13,29

Além disso, o git bisect é esperto o suficiente para conseguir lidar com outras topologias de commits que não são apenas linhas retas, processando merges sem problemas. Legal, não?

Automatizando a bisseção

Você pode escrever um comando, programa ou script para automatizar a sua busca. Se o seu programa retorna 0 quando o commit está OK e 1 quando o commit tem erro, você pode passar ele para o bisect da seguinte forma:

$ git bisect run dotnet test --filter "FullyQualifiedName~ClassNameTests"

Aqui, ser criativo te recompensa. Por exemplo, segue um script que verifica se o tempo de execução de um programa leva mais que 10 segundos:

# measure_10.sh
#!/usr/bin/env bash
set -e

START=$(date +%s)
python3 script.py > /dev/null 2>&1
END=$(date +%s)

ELAPSED=$((END - START))

[ "$ELAPSED" -gt 10 ] && exit 1 || exit 0

Usando ele:

$ git bisect run ./measure_10.sh

Fontes

Esses comandos são explicados em detalhes no livro oficial do Git, o Pro Git, que pode ser encontrado de graça no site do Git e tem tradução parcial para português. Se você gostou, vale a pena dar uma lida e ver :-)


  1. Que nem o Akinator, só que para bugs no seu código. ↩︎

  2. O nome do comando vem daí: ele faz uma bisseção do histórico de commits↩︎