A suíte de testes está verde há três semanas. Hoje o npm test falha em uma asserção que não tem nada a ver com o que você acabou de mudar, e git log v1.4.0..HEAD mostra 340 commits de quatro pessoas desde o último release que alguém tinha certeza de que funcionava. Ler cada diff custa um dia inteiro. Fazer checkout de commits ao acaso é mais rápido, mas ainda não é um plano.

O git bisect transforma isso em uma busca binária. Você dá a ele um commit que sabe ser bom e outro que sabe ser ruim; ele faz checkout do commit no meio do caminho entre os dois e espera você testar esse commit único e reportar o resultado. Cada resposta corta o intervalo pela metade. 340 commits se resolvem em no máximo 9 testes, porque 2^9 = 512.

Como o git bisect reduz a busca pela metade a cada passo

Imagine os 340 commits em uma linha, do mais antigo ao mais novo, a tag boa conhecida em uma ponta e o HEAD quebrado na outra. O git bisect não percorre essa linha. Ele pula para o meio, espera um veredito e então descarta a metade que esse veredito exclui:

1
2
3
4
good                                          bad
 |------------------------|------------------|
                    ^
              teste este

Digamos que o commit do meio passe. O bug está na metade mais recente, então a metade mais antiga desaparece de vez e você não verá mais esses commits nesta sessão. Repita: meio do que sobrou, teste, descarte a metade. Depois de aproximadamente log2(n) rodadas, o intervalo se reduz a um único commit — aquele em que o comportamento passou de funcionando para quebrado. Nem sempre é o commit que “causou” o bug em um sentido mais profundo. É o commit em que o comportamento mudou, o que quase sempre é exatamente o que você precisa.

Como iniciar uma sessão de git bisect

Execute isso a partir de uma árvore de trabalho limpa. O bisect faz checkout de commits sob você, e se recusa a começar com mudanças não commitadas.

1
2
3
git bisect start
git bisect bad HEAD
git bisect good v1.4.0

O Git faz checkout do ponto médio e diz quanto ainda falta:

1
2
Bisecting: 169 revisions left to test after this (roughly 8 steps)
[a1b2c3d4e5f6...] Refactor payment retry logic

Execute seu teste nesse commit e depois reporte o que encontrou:

1
2
npm test
git bisect bad     # o teste falhou aqui

ou

1
git bisect good    # o teste passou aqui

O Git faz checkout do novo ponto médio automaticamente após cada resposta. Continue testando e reportando até não haver mais nada para reduzir:

1
2
3
4
5
6
a1b2c3d4e5f6789... is the first bad commit
commit a1b2c3d4e5f6789...
Author: Priya Nair <[email protected]>
Date:   Tue Sep 2 11:14:02 2026 +0000

    Refactor payment retry logic

Essa é a sua resposta. Leia esse diff, não os oito commits ao redor. git bisect reset te leva de volta ao branch e ao commit de onde você partiu, e os checkouts que o bisect fez pelo caminho não deixam rastro.

1
git bisect reset

Automatizando o ciclo com git bisect run

Responder good ou bad manualmente funciona bem para um bug que você percebe em poucos segundos. Para algo que precisa de uma execução de teste de verdade, entregue todo o ciclo a um script:

1
2
3
4
git bisect start
git bisect bad HEAD
git bisect good v1.4.0
git bisect run ./bisect-test.sh

O bisect-test.sh só precisa terminar com o código certo: 0 significa bom, qualquer código de 1 a 127 exceto 125 significa ruim, e 125 significa “não consigo testar esse commit, pule ele”.

1
2
#!/bin/sh
npm test

A maioria dos test runners já termina com 0 quando passa e com um código diferente de zero quando falha, então muitas vezes o script se resume a chamar o runner. O git bisect run faz checkout de cada ponto médio, executa o script, lê o código de saída e reporta o resultado sozinho, imprimindo no final o mesmo resumo de “first bad commit”.

O script pode fazer mais do que chamar um test runner. Se a regressão só aparece em um artefato construído, um script que roda docker build e depois testa a imagem é algo normal para passar ao git bisect run — os passos de build de Construir e Publicar uma Imagem Docker com GitHub Actions funcionam em um script tão bem quanto em um workflow. Conte com isso: cada rodada agora custa um build completo da imagem.

Há uma restrição para o próprio script: mantenha-o fora do intervalo que você está bisectando, ou garanta que ele não mudou dentro dele. Se o script faz parte do histórico pesquisado, um commit antigo faz checkout de uma versão antiga do script junto com o código antigo, e você deixa de rodar o mesmo teste a cada passo.

Pulando commits que você não consegue testar com git bisect skip

Alguns commits não podem ser testados sozinhos: um commit no meio de um refactoring que não compila, uma migração de esquema que precisa de um estado de banco de dados que você não tem. Forçar um veredito good/bad em um desses envenena o resultado. Exclua-o em vez disso:

1
git bisect skip

O bisect faz checkout de um commit próximo e continua contornando a lacuna. No bisect run, o código de saída 125 faz a mesma coisa automaticamente, então um script capaz de identificar “esse commit nem compila” pode pulá-lo em vez de reportar um bad falso. Pule commits adjacentes demais e o bisect fica sem terreno testável: a resposta final vira “o bug vem de um desses N commits” em vez de um commit único.

Quando o git bisect não é a ferramenta certa

O bisect presume que seu teste é determinístico: mesmo commit, mesmo resultado, sempre. Um teste instável quebra essa suposição silenciosamente. O bisect não consegue distinguir “esse commit está quebrado” de “esse teste falhou por instabilidade”, e uma resposta errada no início manda cada rodada seguinte para a metade errada. Conserte ou exclua a asserção instável antes de bisectar, não depois.

Ele também presume que um commit produziu uma única mudança de comportamento limpa. Um bug que se infiltrou ao longo de vários commits, ou que depende de um estado acumulado em vez de um único diff, não converge para nada útil. Um histórico muito rebasado é uma versão mais leve do mesmo problema (veja Git Rebase vs Merge: diferenças e quando usar cada um): o primeiro commit ruim que o bisect encontra pode ser um commit squashado que agrupa cinco mudanças lógicas. Correto, mas grosso demais para revisar isoladamente.

Nenhum dos dois casos pede uma ferramenta diferente, só um ponto de partida diferente. Primeiro deixe o teste confiável, depois escolha um commit good distante o suficiente para ter certeza de que o bug ainda não estava ali.

Da próxima vez que surgir uma regressão sem causa óbvia, marque os dois extremos e responda good ou bad algumas vezes — ou escreva o script de três linhas e deixe o git bisect run responder por você.

Artigos relacionados