Fundamentos de Programação/02 - Código que Presta/Semana 08 - Git Além do Básico5 min
Bisect
- 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 rune 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:
- um teste objetivo — você precisa saber dizer "bom" ou "ruim" sem ambiguidade
- monotonicidade — era bom, ficou ruim, e não volta a ser bom. Bug intermitente quebra a premissa e o bisect aponta o commit errado
- 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 resetdeixa você num commit detached, e a próxima confusão é garantida. bisect runcom 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
- 1. Busca Binária — semana 4, o mesmo algoritmo, sobre commits
- 4. Refatorações Nomeadas — semana 6, commits pequenos que compilam são o pré-requisito
- 1. Rebase vs Merge — squash reduz a resolução do bisect
- 6. Pilha de Chamadas — semana 3, quando o stack trace não basta, bisecte
Parte de Semana 08 - Git Além do Básico · 00 - MOC Fundamentos de Programação