Bisect: encontrando o commit que introduziu um bug

1. O que é o Git Bisect e por que usá-lo?

O Git Bisect é uma ferramenta de busca binária automatizada que percorre o histórico de commits de um repositório para identificar exatamente qual commit introduziu um bug ou comportamento indesejado. Em vez de verificar manualmente dezenas ou centenas de commits, o bisect reduz o problema a uma complexidade O(log n), onde n é o número de commits entre o ponto "bom" (sem o bug) e o ponto "ruim" (com o bug).

O cenário típico é familiar: você descobre que um bug existe na versão atual (HEAD), mas sabe que ele não estava presente em uma versão anterior (por exemplo, v1.0). Em vez de inspecionar cada commit manualmente, o bisect divide o intervalo ao meio repetidamente até isolar o commit culpado. Com 1000 commits, você precisa testar apenas cerca de 10 commits — uma economia dramática de tempo e esforço.

2. Preparando o ambiente para o bisect

Antes de iniciar o bisect, você precisa identificar dois pontos no histórico:

  • Commit "bom": um commit onde o bug não existe
  • Commit "ruim": um commit onde o bug está presente (geralmente o HEAD)

Para encontrar esses commits, você pode usar tags, referências relativas ou hashes:

# Encontrar commits relevantes
git log --oneline --all

# Exemplo: tag v1.0 é boa, HEAD é ruim
git log --oneline v1.0..HEAD

Certifique-se de que seu repositório está limpo — sem alterações não commitadas ou arquivos não rastreados que possam interferir nos testes:

git status
# Se houver alterações, faça stash ou commit
git stash

Dicas para escolher os pontos de referência:

  • Use tags semânticas: v1.0, v2.3.1
  • Use referências relativas: HEAD~50 (50 commits atrás)
  • Use hashes específicos: a1b2c3d4

3. Iniciando e executando o bisect passo a passo

O processo manual do bisect segue um ciclo claro. Primeiro, inicie o bisect e marque os pontos inicial e final:

# Iniciar o bisect
git bisect start

# Marcar o commit ruim (onde o bug existe)
git bisect bad HEAD

# Marcar o commit bom (onde o bug não existe)
git bisect good v1.0

O Git então calcula o ponto médio e faz checkout automaticamente para esse commit:

Bisecting: 47 revisions left to test after this (roughly 6 steps)
[3a4b5c6d] Commit message do ponto médio

Agora você testa esse commit. Compile, execute os testes relevantes e verifique se o bug está presente:

# Exemplo: compilar e testar
make compile
./run_tests.sh

# Se o bug não está presente neste commit:
git bisect good

# Se o bug está presente neste commit:
git bisect bad

O Git continua dividindo o intervalo até restar apenas um commit — o culpado.

4. Automatizando o processo com scripts

Para projetos com testes automatizados, o comando git bisect run elimina o trabalho manual. Você fornece um script que retorna 0 para "bom" e qualquer outro código (geralmente 1) para "ruim":

# Criar script de teste
cat > test_bug.sh << 'EOF'
#!/bin/bash
make compile && ./run_tests.sh
if [ $? -eq 0 ]; then
    exit 0  # Bom: testes passam
else
    exit 1  # Ruim: testes falham
fi
EOF

chmod +x test_bug.sh

# Executar bisect automatizado
git bisect start HEAD v1.0
git bisect run ./test_bug.sh

O Git executa o script para cada commit no intervalo. Quando o script retorna 1 (falha), o Git marca o commit como "ruim" e continua a busca. O resultado final é exibido automaticamente:

3a4b5c6d is the first bad commit
commit 3a4b5c6d
Author: João Silva <joao@exemplo.com>
Date:   Mon Jan 15 14:30:00 2024 -0300

    Refatoração do módulo de autenticação

Cuidados importantes com scripts automatizados:

  • O script deve ser determinístico — mesmo commit, mesmo resultado
  • Evite dependências de estado externo (arquivos temporários, rede)
  • Garanta que o script lide com erros de compilação adequadamente

5. Lidando com situações especiais

Commits que não compilam

Às vezes, commits intermediários podem estar em um estado inconsistente. Use git bisect skip para pular esses commits:

git bisect skip
# Ou pular commits específicos
git bisect skip a1b2c3d e4f5g6h

Bugs intermitentes

Para bugs que aparecem e desaparecem, o bisect pode ser impreciso. Estratégias:

  • Execute o script várias vezes e use a maioria dos resultados
  • Use git bisect skip para commits com resultados inconsistentes
  • Considere usar um script que detecta o bug de forma mais robusta

Múltiplos pontos de referência

Você pode refinar a busca marcando commits adicionais como bons ou ruins:

git bisect good v1.1  # Outro commit bom conhecido
git bisect bad v2.0   # Outro commit ruim conhecido

6. Finalizando e interpretando os resultados

Quando o bisect encontra o commit culpado, você precisa sair do modo bisect e analisar o resultado:

# Sair do modo bisect e voltar ao HEAD original
git bisect reset

# Analisar o commit culpado
git show 3a4b5c6d

# Ver o diff completo
git diff 3a4b5c6d^..3a4b5c6d

# Ver arquivos alterados
git show --stat 3a4b5c6d

Para confirmar se o bug foi realmente introduzido naquele commit, você pode verificar manualmente:

# Checkout no commit anterior
git checkout 3a4b5c6d^
# Testar se o bug não está presente

# Checkout no commit suspeito
git checkout 3a4b5c6d
# Testar se o bug está presente

7. Boas práticas e dicas avançadas

Integração com CI/CD

Muitos sistemas de CI permitem acionar um bisect automaticamente quando um bug é detectado. Exemplo com GitHub Actions:

# workflow.yml
jobs:
  bisect:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Run bisect
        run: |
          git bisect start HEAD v1.0
          git bisect run ./test_bug.sh

Visualizando o progresso

Use git bisect visualize para ver o estado atual do bisect:

git bisect visualize
# Abre o gitk ou git log mostrando os commits marcados

Limitações importantes

  • Histórico não linear: bisect funciona melhor em históricos lineares. Merges complexos podem confundir o algoritmo
  • Bisect em branches: você pode iniciar o bisect em qualquer branch, mas os resultados podem ser imprecisos se os branches divergiram significativamente
  • Commits de merge: o bisect pode pular commits de merge se eles não introduzirem mudanças de código

Dicas avançadas

  • Use git bisect replay <arquivo> para repetir uma sessão de bisect anterior
  • Combine bisect com git blame para identificar o autor do commit culpado
  • Para projetos grandes, considere usar git bisect skip em commits que alteram apenas documentação ou formatação

O Git Bisect é uma ferramenta poderosa que transforma a caça a bugs de uma tarefa frustrante em um processo sistemático e eficiente. Com prática, você pode reduzir o tempo de diagnóstico de horas para minutos.

Referências