Git bisect run: automatizando busca de regressões com scripts

1. Introdução ao git bisect e o problema das regressões

1.1. O que é uma regressão e por que encontrá-la manualmente é custoso

Uma regressão ocorre quando uma funcionalidade que funcionava corretamente em versões anteriores do código passa a apresentar falhas. Em projetos com dezenas ou centenas de commits entre releases, identificar manualmente o commit que introduziu o bug pode consumir horas ou dias de trabalho. Cada tentativa de compilar, testar e verificar manualmente um commit intermediário é propensa a erros humanos e exige atenção constante.

1.2. Funcionamento básico do git bisect manual: busca binária entre commits bons e ruins

O Git oferece o comando git bisect para realizar uma busca binária eficiente. Você informa um commit conhecido como "bom" (onde a funcionalidade funcionava) e um commit "ruim" (onde a funcionalidade está quebrada). O Git então calcula o ponto médio do intervalo, faz checkout nesse commit e aguarda sua decisão manual:

git bisect start
git bisect bad v2.0    # commit ruim
git bisect good v1.0   # commit bom
# Git faz checkout em um commit intermediário
# Você testa manualmente e responde:
git bisect good        # se funcionar
# ou
git bisect bad         # se falhar

Após aproximadamente log2(n) testes, o Git identifica o primeiro commit ruim.

1.3. Limitações da busca manual: repetição, erro humano e casos com muitos commits

A busca manual exige que você pare o trabalho a cada etapa, execute testes, interprete resultados e informe o Git. Em intervalos com 200 commits, são necessários cerca de 8 testes manuais. Cada interrupção quebra o fluxo de trabalho e aumenta a chance de erro. Para projetos com 1000 commits, são 10 iterações — tedioso e propenso a enganos.

2. Automatizando com git bisect run: conceito e preparação

2.1. O que é git bisect run e como ele substitui a interação manual

O git bisect run automatiza todo o processo. Em vez de você testar manualmente cada commit intermediário, o Git executa um script que você fornece. O script deve retornar um código de saída que indique se o commit atual é bom ou ruim:

  • Código 0: commit bom (funcionalidade funciona)
  • Código 1-127 (exceto 125): commit ruim (funcionalidade quebrada)
  • Código 125: commit não testável (pula para o próximo)

2.2. Estrutura de um script de teste: saída zero (sucesso/bom) vs. saída não-zero (falha/ruim)

O script pode ser qualquer executável (shell, Python, Ruby, etc.). O importante é que ele retorne o código de saída apropriado. Exemplo mínimo:

#!/bin/bash
# script_test.sh
make test  # se falhar, retorna código != 0

Se make test falhar, o script retorna código não-zero, indicando commit ruim. Se passar, retorna zero.

2.3. Pré-requisitos: commit inicial "bom" e commit "ruim" conhecidos

Antes de iniciar, você precisa identificar:
- Um commit onde a funcionalidade funcionava (bom)
- Um commit onde a funcionalidade está quebrada (ruim)

Use git log --oneline para encontrar os hashes ou tags apropriados.

3. Escrevendo scripts eficazes para bisect run

3.1. Scripts simples: comandos shell únicos

Para projetos com testes rápidos, um comando direto funciona:

git bisect start HEAD v1.0
git bisect run sh -c "make && ./run_tests"

O sh -c permite executar múltiplos comandos em sequência. Se make falhar, o script já retorna erro.

3.2. Scripts complexos: compilação, execução de testes e validação de saída

Para projetos que exigem compilação e validação específica, crie um script separado:

#!/bin/bash
# bisect_script.sh
set -e  # interrompe em qualquer erro

# 1. Compilar
make clean
make

# 2. Executar teste específico
./test_suite --test=feature_x > /tmp/test_output.txt 2>&1

# 3. Validar saída
grep -q "PASS" /tmp/test_output.txt

O set -e garante que qualquer comando com falha interrompa o script com código de erro.

3.3. Tratamento de erros: como lidar com dependências ausentes ou builds quebrados (exit 125)

Commits intermediários podem ter ambientes de build incompletos ou dependências ausentes. Use exit 125 para pular esses commits:

#!/bin/bash
if ! make configure; then
    echo "Configuração falhou, pulando commit"
    exit 125
fi
make
./run_tests

O Git então marcará o commit como não testável e continuará a busca em outro ramo.

4. Exemplo prático: caçando um bug de funcionalidade

4.1. Cenário: feature que parou de funcionar entre versões – definindo os pontos bom e ruim

Suponha um projeto com tags v1.0 (funciona) e v2.0 (quebrado). O bug está em uma função de cálculo de frete.

4.2. Construção de um script que compila o projeto e executa um teste unitário específico

Crie o arquivo test_frete.sh:

#!/bin/bash
cd /tmp/projeto
make clean
make 2>/dev/null

# Executa teste específico
./bin/test_frete --test=calculo_frete
exit $?

4.3. Execução passo a passo: git bisect start, git bisect bad, git bisect good, git bisect run ./script.sh

git bisect start
git bisect bad v2.0
git bisect good v1.0
git bisect run ./test_frete.sh

O Git exibirá o progresso:

Bisecting: 7 revisions left to test after this (roughly 3 steps)
[abc1234] Commit mensagem
...
abc1234 is the first bad commit

O commit abc1234 é o culpado. Você pode inspecioná-lo com git show abc1234.

5. Casos especiais e boas práticas

5.1. Scripts que dependem de estado externo: limpeza de cache, reset de banco de dados

Se seu teste depende de banco de dados ou cache, limpe o estado antes de cada execução:

#!/bin/bash
rm -rf /tmp/test_db
./setup_test_db.sh
make test

5.2. Comportamento com merge commits e histórico não linear

O git bisect lida bem com merges, seguindo os pais dos commits. Se um merge introduzir o bug, o bisect identificará o merge commit. Para evitar confusão, use git bisect --first-parent se quiser ignorar merges de branches laterais.

5.3. Usando git bisect skip dentro do script para commits não testáveis

Além do exit 125, você pode usar git bisect skip diretamente no script se detectar condições especiais:

if [ ! -f "Makefile" ]; then
    git bisect skip
    exit 0  # não importa, skip já foi chamado
fi

6. Integração com CI/CD e pipelines automatizados

6.1. Disparando bisect run em servidores de integração contínua

Em pipelines CI, você pode executar o bisect automaticamente quando uma regressão é detectada:

# Jenkins/GitLab CI
git bisect start HEAD $LAST_KNOWN_GOOD
git bisect run ./ci_test.sh

6.2. Salvando e reutilizando sessões de bisect com git bisect log e git bisect replay

Para interromper e retomar uma sessão:

git bisect log > bisect_log.txt
# ... depois ...
git bisect replay bisect_log.txt

Isso é útil em sessões longas ou quando o servidor CI reinicia.

6.3. Notificações automáticas quando o commit culpado é encontrado

Combine com scripts de notificação:

#!/bin/bash
git bisect run ./test.sh
if [ $? -eq 0 ]; then
    BAD_COMMIT=$(git bisect visualize --oneline | head -1)
    curl -X POST https://api.slack.com/notify -d "Bug encontrado em $BAD_COMMIT"
fi

7. Limitações e alternativas ao git bisect run

7.1. Limitações: scripts lentos, dependência de ambiente, falsos positivos

  • Scripts lentos tornam o processo demorado (cada commit precisa ser compilado e testado)
  • Dependências de ambiente podem causar falsos positivos se o ambiente mudar entre commits
  • Testes não determinísticos (ex: race conditions) podem indicar falsos culpados

7.2. Alternativas: git blame combinado com análise manual, ferramentas de rastreamento de regressão

  • git blame identifica quem modificou cada linha, mas não encontra automaticamente o commit que introduziu o bug
  • Ferramentas como git bisect com git log --graph ajudam a visualizar o histórico
  • Sistemas de CI com rastreamento de regressão (ex: Jenkins com plugin de bisect)

7.3. Quando evitar: projetos com testes não determinísticos ou infraestrutura instável

Evite usar bisect run se seus testes falham esporadicamente por razões ambientais (rede instável, banco de dados compartilhado). Nesses casos, prefira a análise manual combinada com git bisect interativo.

8. Conclusão e próximos passos na série

8.1. Recapitulação: como bisect run acelera a depuração de regressões

O git bisect run transforma a busca manual de regressões em um processo automatizado e confiável. Com um script bem escrito, você pode identificar o commit culpado em minutos, mesmo em projetos com centenas de commits.

8.2. Conexão com temas vizinhos: hotfix workflow e feature flags como prevenção de regressões

Combinar bisect run com boas práticas como hotfix branches e feature flags reduz drasticamente o impacto de regressões. Feature flags permitem desligar funcionalidades problemáticas sem reverter commits.

8.3. Exercício prático sugerido: aplicar bisect run em um repositório real com histórico de bugs

Clone um repositório open source com histórico conhecido de bugs (ex: um projeto que você contribui). Identifique um bug reportado entre duas versões e use bisect run para encontrar o commit culpado. Documente o processo e compartilhe os resultados.

Referências