Tutorial

git bisect: como encontrar o commit que quebrou o código

Atualizado: · Skillaria.Top

O git bisect encontra o commit em que um bug apareceu fazendo uma busca binária no histórico. Rode git bisect start, marque o commit ruim com git bisect bad e um que com certeza funcionava com git bisect good <hash>, depois teste os commits sugeridos até o Git apontar o culpado; no fim, git bisect reset.

O git bisect procura o commit que introduziu um erro usando busca binária. Você indica um commit em que o bug existe e outro em que ele não existia — o Git leva você para o meio do intervalo e pergunta: «aqui está bom ou ruim?». Cada resposta elimina metade dos candidatos, por isso entre 1000 commits o culpado aparece em umas 10 verificações.

Isso vale ouro quando o erro não é óbvio: um teste falhou, a performance caiu, o layout quebrou e pelos diffs não dá para entender nada. Em vez de ler centenas de alterações, você responde à mesma pergunta simples algumas vezes.

Como funciona a busca binária no histórico

Suponha que entre a versão estável v2.3 e o main atual haja 200 commits. O Git pega o commit do meio e você o testa. Se ali já está quebrado, o erro surgiu na primeira metade; se não, na segunda. O número de candidatos cai pela metade a cada passo: 200, 100, 50, 25, 13, 7, 4, 2, 1. Oito verificações em vez de duzentas.

Daí vem o requisito principal: a propriedade «quebrado» precisa ser monotônica. Se o bug apareceu, sumiu e voltou, o bisect vai achar uma das transições, mas não necessariamente a que você quer.

git bisect passo a passo: modo manual

1. Encontre um commit bom e um ruim

O ruim costuma ser o HEAD atual. O bom é qualquer commit em que tudo com certeza funcionava: a tag do último release, um commit de uma semana atrás. Para achar um candidato, use git log --oneline, git tag ou git log --since="2 weeks ago".

2. Inicie a sessão

git bisect start
git bisect bad                 # o commit atual está quebrado
git bisect good v2.3           # no release v2.3 tudo funcionava
Bisecting: 99 revisions left to test after this (roughly 7 steps)
[4f5e6d7c...] Atualizar dependências do frontend

Dá para escrever o mesmo em uma linha: git bisect start HEAD v2.3 — primeiro o ruim, depois o bom. O Git muda o diretório de trabalho na hora para o commit do meio (em estado de detached HEAD).

3. Teste e marque

Faça o build do projeto, rode o teste ou reproduza o bug manualmente. Depois informe o resultado:

git bisect good     # aqui funciona
Bisecting: 49 revisions left to test after this (roughly 6 steps)

git bisect bad      # aqui está quebrado
Bisecting: 24 revisions left to test after this (roughly 5 steps)

4. Veja o resultado

Quando sobrar um único candidato, o Git o exibe:

a1b2c3d4e5f6... is the first bad commit
commit a1b2c3d4e5f6...
Author: João Silva <joao@example.com>
Date:   Tue Sep 29 11:40:12 2026 -0300

    Fazer cache do cálculo do carrinho

 src/cart.js | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

5. Encerre a sessão

git bisect reset

O comando leva você de volta para a branch onde começou. Não pule esse passo: enquanto a sessão não for encerrada, você continua em detached HEAD, e commits novos se perdem com facilidade. A variante git bisect reset HEAD deixa você no commit encontrado, e git bisect reset <commit>, em qualquer commit indicado.

Busca automática: git bisect run

Se a verificação cabe em um comando, deixe todo o trabalho com o Git. O git bisect run executa um script a cada passo e marca o commit sozinho conforme o código de saída:

Código de saídaSignificado
0good — o commit está bom
1–127, exceto 125bad — o commit está ruim
125skip — o commit não pode ser testado; pular
acima de 127 (ou <0)interromper o bisect (por exemplo, o script morreu por um sinal)

Exemplos de uso:

git bisect start HEAD v2.3
git bisect run npm test
git bisect run pytest tests/test_cart.py -x
git bisect run make check

Para verificações mais complexas, escreva um script pequeno. É importante separar «não compilou» de «o bug se reproduziu», senão o bisect confunde um build quebrado com o erro que você procura:

#!/bin/sh
# check.sh
npm ci --silent || exit 125      # não conseguiu instalar as dependências — pular
npm run build   || exit 125      # não compilou — pular
node scripts/check-cart-total.js # 0 — ok, 1 — bug
chmod +x check.sh
git bisect run ./check.sh

Mantenha o script fora dos arquivos rastreados ou numa pasta que exista em todos os commits testados: o bisect troca o diretório de trabalho, e um arquivo adicionado recentemente some nos commits antigos. O mais seguro é colocá-lo em /tmp ou em qualquer pasta fora do repositório.

Um teste que não existia antes

Situação comum: você escreveu um teste que reproduz o bug, mas nos commits antigos esse arquivo não existe. A solução é a mesma — uma cópia fora do repositório:

cp tests/test_regression.py /tmp/
git bisect run pytest /tmp/test_regression.py

git bisect skip: quando o commit não pode ser testado

Às vezes o commit do meio não compila ou falha por outro motivo. Marcá-lo como good ou bad distorceria o resultado. Pule-o:

git bisect skip
git bisect skip v2.4..v2.4.3   # pular um intervalo

O Git escolhe um commit vizinho. Se os commits pulados ficarem bem na fronteira, no fim você verá uma lista de «possíveis culpados» em vez de um só — aí é olhar os diffs deles manualmente.

Termos próprios no lugar de good e bad

O bisect não serve só para bugs. Por exemplo, você quer achar o commit a partir do qual a página passou a carregar mais rápido. Chamar commits rápidos de «ruins» confunde, por isso existem palavras alternativas:

git bisect start --term-old=slow --term-new=fast
git bisect fast HEAD
git bisect slow v2.3

Os sinônimos embutidos são old e new: git bisect new, git bisect old. A regra é uma só: o estado «antigo» tem que estar no commit mais antigo.

Registro da sessão: log, replay, visualize

  • git bisect log — mostra todas as suas marcações. Salve num arquivo se a sessão for longa.
  • git bisect replay arquivo — repete a sessão a partir do registro. Errou no sexto passo? Salve o log, apague as linhas erradas, rode git bisect reset e depois replay.
  • git bisect visualize (ou git bisect view) — mostra os candidatos restantes; com --oneline, exibe-os via git log.
git bisect log > /tmp/bisect.log
# editamos o log
git bisect reset
git bisect replay /tmp/bisect.log

Bisect e commits de merge

Num histórico com muitos merges, o bisect pode levar você para dentro de uma branch mesclada — para commits criados antes de a branch ser sincronizada com o main e, portanto, montados sobre outra base. Se o que importa primeiro é descobrir qual merge trouxe o bug, use:

git bisect start --first-parent

Assim o Git segue só pelos primeiros pais dos commits de merge, ou seja, pelo histórico da branch principal. Depois de achar o merge culpado, dá para rodar um segundo bisect dentro da branch correspondente.

Erros comuns

  • Trocou good e bad. O resultado não fará sentido. Recupere a sessão com git bisect log e replay, sem começar do zero.
  • Diretório de trabalho sujo. Alterações não commitadas atrapalham a troca de commits e afetam o teste. Antes de começar, faça um commit ou um git stash.
  • Artefatos de build não limpos. Cache velho ou node_modules de outro commit geram resultados falsos. No script, faça um build limpo.
  • Teste instável (flaky). Se o teste às vezes falha sozinho, o bisect marca um commit aleatório. Rode a verificação várias vezes ou conserte o teste antes da busca.
  • Esqueceu o reset. Ficou em detached HEAD e começou a trabalhar. O reflog ajuda a achar os commits perdidos.

Exemplo: caçando uma regressão do início ao fim

Usuários relataram que o total do carrinho sai errado quando há desconto. No release v2.3 estava certo; no main, não. Escrevemos uma verificação mínima e iniciamos a busca:

cat > /tmp/check.sh <<'SH'
#!/bin/sh
npm ci --silent || exit 125
node -e "const c=require('./src/cart'); process.exit(c.total([{price:100}], 0.1) === 90 ? 0 : 1)"
SH
chmod +x /tmp/check.sh

git stash                       # guardar as alterações não commitadas
git bisect start HEAD v2.3
git bisect run /tmp/check.sh
...
a1b2c3d4e5f6... is the first bad commit
git bisect log > /tmp/cart-bisect.log
git bisect reset
git stash pop

A busca inteira em uma centena de commits roda sem você, enquanto você cuida de outra coisa. O log salvo é útil se for preciso mostrar aos colegas, na tarefa, como exatamente o culpado foi encontrado.

O que fazer com o commit encontrado

Achado o culpado, veja-o por inteiro: git show a1b2c3d. Um commit pequeno, com mensagem clara, já entrega a causa — é um dos motivos por que as equipes valorizam commits atômicos: bisect num histórico de enormes «fix stuff» é quase inútil. Daí há dois caminhos: corrigir o erro com um novo commit ou, se a mudança inteira foi um equívoco e já está publicada, desfazê-la com git revert a1b2c3d.

Resumo

O git bisect transforma a caça a uma regressão, que seria ler centenas de diffs, em algumas respostas «funciona / não funciona» — e, com git bisect run, em um único comando. Você só precisa de duas referências — um commit bom e um ruim — e de uma verificação reproduzível. Leitura do histórico, navegação entre commits e busca de erros são trabalhadas na prática no curso de Git.

Perguntas frequentes

Quantos passos o git bisect leva?

Cerca de log2 do número de commits no intervalo: uns 7 passos para 100 commits e uns 10 para 1000. O próprio Git mostra a estimativa roughly N steps.

Dá para rodar o bisect sem saber qual commit é bom?

É preciso pelo menos um. Pegue um commit sabidamente antigo ou a tag de um release e teste-o manualmente; os passos extras por causa de um intervalo maior quase não custam nada.

O git bisect altera o histórico do repositório?

Não, ele só troca o diretório de trabalho entre commits e guarda as marcações em referências internas refs/bisect, que são apagadas no git bisect reset.

Posso indicar vários commits bons?

Sim, o git bisect good aceita vários commits, o que estreita o intervalo, especialmente em históricos com muitas branches.

O bisect serve só para achar bugs?

Não, ele encontra qualquer momento em que uma propriedade mudou: quando um arquivo apareceu, o build ficou mais rápido ou um comportamento mudou. Para isso, use os termos old/new ou os seus próprios com --term-old e --term-new.

Aprenda este tema em vídeo

Este tema é abordado em profundidade, com prática, no curso “Git do zero ao desenvolvedor confiante”.

Ir para o curso