trilha

Fundamentos de Programação/02 - Código que Presta/Semana 08 - Git Além do Básico5 min

Bisect

Perguntas-guia
  • Que pergunta o bisect responde, e por que ela custa O(log n) commits?
  • O que ele exige do seu histórico para funcionar bem? (commits que compilam)
  • Como automatizar com git bisect run e um script que retorna 0 ou 1?
  • Qual a relação disso com "pegar um bug em código que você nunca viu"?

Conceito

Bisect responde uma pergunta: qual foi o primeiro commit em que isso quebrou?

E responde por busca binária sobre o histórico (1. Busca Binária). Com 1.000 commits entre o bom conhecido e o ruim conhecido, são 10 testes — não 1.000.

Os pré-requisitos, e cada um é uma condição real:

  1. um teste objetivo — você precisa saber dizer "bom" ou "ruim" sem ambiguidade
  2. monotonicidade — era bom, ficou ruim, e não volta a ser bom. Bug intermitente quebra a premissa e o bisect aponta o commit errado
  3. commits que constroem — se metade dos commits não compila, você passa o tempo dando git bisect skip

O item 3 é a ligação direta com 4. Refatorações Nomeadas: commits pequenos que compilam não são higiene estética. São o que faz esta ferramenta funcionar.

A relação com "pegar um bug em código que você nunca viu" — item da Autoavaliação — é o que torna o bisect especial: ele te dá o commit culpado sem que você entenda o código. Você troca compreensão por bissecção. Depois de saber qual diff quebrou, entender fica muito mais fácil.

Na prática

Cenário verificado: 10 commits, o bug plantado no sexto (uma saída que mudou de 10 para 99), e um script de teste.

# teste.sh — exit 0 = bom, exit 1 = ruim
#!/bin/sh
[ "$(./prog.sh)" = "10" ] && exit 0 || exit 1
$ git bisect start HEAD HEAD~9      # HEAD é ruim, HEAD~9 é bom
$ git bisect run ./teste.sh
Bisecting: 2 revisions left after this (roughly 1 step)
Bisecting: 0 revisions left after this (roughly 0 steps)
7a16f7e is the first bad commit
    c6: refatora saida

Três testes para localizar entre dez commits. E git bisect log guarda o processo inteiro, reproduzível.

Os códigos de saída que bisect run interpreta:

Saída do script Significado
0 commit bom
1 a 124 (exceto 125) commit ruim
125 pule este commit (não dá para testar)
126, 127 erro ao executar o script — aborta

O 125 é o que salva quando um commit não compila:

go build ./... || exit 125     # não compila: pula em vez de marcar ruim
go test ./... || exit 1        # compila e falha: ruim
exit 0

Sem essa linha, um commit que não compila é marcado ruim e o bisect aponta para o lugar errado.

Respostas às perguntas-guia

1. Que pergunta o bisect responde, e por que ela custa O(log n) commits?

"Qual o primeiro commit ruim?" Custa log₂ n porque cada teste descarta metade do intervalo — é 1. Busca Binária com o commit no lugar do índice.

2. O que ele exige do seu histórico para funcionar bem?

Commits que constroem e uma propriedade monotônica. Histórico de commits gigantes reduz a precisão (o culpado é um diff de 2.000 linhas); histórico que não compila te obriga a skip constante.

Squash de 15 commits em 1 transforma 15 pontos de busca em 1 — é o custo escondido da política de squash (1. Rebase vs Merge).

3. Como automatizar com git bisect run e um script que retorna 0 ou 1?

#!/bin/sh
go build ./... || exit 125      # não compila -> pula
go test -run TestQueQuebrou ./... || exit 1
exit 0
git bisect start <ruim> <bom>
git bisect run ./bisseca.sh
git bisect reset

O script precisa ser executável e fora do controle de versão do range (senão o checkout o substitui em cada passo). Guarde em /tmp e chame por caminho absoluto.

4. Qual a relação disso com "pegar um bug em código que você nunca viu"?

É a resposta direta. Bisect não exige entender o código — exige saber reconhecer o sintoma. Você obtém o diff culpado primeiro e entende depois, que é a ordem inversa (e muito mais eficiente) do debugging por leitura.

Armadilhas

  • Bug intermitente quebra a monotonicidade e o bisect aponta um commit arbitrário. Rode o teste várias vezes por commit (for i in 1 2 3; do ... done) antes de decidir.
  • Script versionado é substituído a cada checkout do bisect. Mantenha fora do repo.
  • Estado externo contamina: banco migrado, cache, node_modules, artefato de build. O script deve limpar antes de testar.
  • Commit que não compila marcado como ruim (esqueceu o exit 125) leva ao commit errado, com confiança.
  • Esquecer git bisect reset deixa você num commit detached, e a próxima confusão é garantida.
  • bisect run com script que sempre falha marca tudo como ruim e aponta o primeiro commit do range. Teste seu script manualmente nos dois extremos antes.

Comandos essenciais

git bisect start                    # inicia
git bisect bad                      # HEAD atual é ruim
git bisect good v1.2.0              # esta tag era boa
git bisect start HEAD v1.2.0        # os dois de uma vez (ruim, bom)
git bisect run /tmp/bisseca.sh      # automatizado
git bisect skip                     # este commit não dá para testar
git bisect log                      # o processo, reproduzível
git bisect replay log.txt           # refaz uma bissecção
git bisect reset                    # volta ao normal
git bisect start -- caminho/        # limita aos commits que tocaram um caminho

O -- caminho/ no final é subestimado: bissecar só os commits que mexeram no diretório relevante reduz o range antes de começar.

Relacionado


Parte de Semana 08 - Git Além do Básico · 00 - MOC Fundamentos de Programação

Buscar

Busca por título, seção e texto das notas