Encontrando erros com Git Bisect

Um cenário que quase não acontece no nosso dia-a-dia é alguém enviar um commit que quebra o build do projeto. Mesmo com uma bateria de testes e um servidor de integração contínua, aparece um commit que quebra tudo.

Uma das várias ferramentas a disposição de quem precisa manter um olho no build do projeto pra garantir que tudo está green é o comando bisect do git.

Como funciona

Imagine que você tem dois pontos no tempo, hoje, onde o build está vermelho, e ontem, onde você viu que o build estava verde antes de sair do escritório. Em algum momento entre esses dois pontos algum commit quebrou o build e todo mundo recebeu um e-mail do jenkins. Como encontrar o exato momento onde o build quebrou?

Uma das diversas formas é realizar uma busca binária nos commits do projeto. Então você tem um certo commit de ontem, que você sabe que está funcionando, e o commit atual, que está quebrado. O que você pode fazer é procuar os commits entre estes dois e executar um git checkout para verificar se os testes passam, certo?

Essa é a ideia por trás do git bisect. Apesar de simples, ela é bem útil, pois realiza esse processo pra você. Ao iniciar um git bisect você indica um commit que contém um estado bom do seu repositório (good) e um commit que contém um estado ruim do seu repositório (bad). Com isso o git vai realizando checkouts, seguindo uma busca binária, e você pode indicar se o estado é bom ou ruim. Ao final, o git lhe diz qual commit danificou o repositório.

Mão na massa

Para exemplificar o uso do git bisect, considere o seguinte repositório (https://github.com/MarcosX/git_bisect_tutorial). É um repositório com código ruby e alguns testes com rspec. Se você clonar o repositório, verá que os testes não estão passando.

Uma olhada no git log mostra a seguinte saída:

git_bisect_example (master) $ git lg
* fe41160 (HEAD, origin/master, master) Refactoring evaluate
* cff0b74 Adding tie test case
* eda39b0 Refactoring evluator
* 4a35ef0 Player 2 wins
* 63735b1 Refactoring evaluate
* a6a5224 Player 1 wins
* 64432c0 First commit
* 7ace5bc Initial commit

Sabemos que no commit onde o master está apontando os testes estão falhando. Também podemos assumir que o primeiro commit os testes estão passando, provavelmente nem existam testes ainda. Então vamos começar com o git bisect:

git_bisect_example (master) $ git bisect start
git_bisect_example (master) $ git bisect good 7ace5bc
git_bisect_example (master) $ git bisect bad master
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[63735b13802427f9326b3469a41607849335bce6] Refactoring evaluate

O primeiro comando inicia o processo de rastreamento de commits do git. Em seguida indicamos um commit onde os testes estão passando, utilizando o SHA do commit (no exemplo o commit foi 7ace5bc Initial commit). Logo abaixo indicamos um commit onde os testes estão falhando (no exemplo o commit onde o master está apontando). Após isso, o git informa que existem 3 possíveis revisões para serem analisadas e informa que atualmente o master está apontando para o commit “63735b1 Refactoring evaluate”. Um git log mostra isso mais claramente:

git_bisect_example ((no branch)) $ git lg
* 63735b1 (HEAD) Refactoring evaluate
* a6a5224 Player 1 wins
* 64432c0 First commit
* 7ace5bc (refs/bisect/good-7ace5bc4fc9f3830e8a067216ce5607df65c9f8c) Initial commit

Ou seja, o git reduziu o repositório pela metade. Agora precisamos indicar se esse commit é bom ou ruim. Após rodar os testes vemos que tudo passa, então esse é um bom commit:

git_bisect_example ((no branch)) $ git bisect good
Bisecting: 1 revision left to test after this (roughly 1 step)
[eda39b0c80b4d66ad2372a0b3302887c0c3486d7] Refactoring evluator

Como o commit foi bom, então o git sabe que o commit que introduziu o problema está na outra metade do repositório. Uma olhada no git log mostra:

git_bisect_example ((no branch)) $ git lg
* eda39b0 (HEAD) Refactoring evluator
* 4a35ef0 Player 2 wins
* 63735b1 (refs/bisect/good-63735b13802427f9326b3469a41607849335bce6) Refactoring evaluate
* a6a5224 Player 1 wins
* 64432c0 First commit
* 7ace5bc (refs/bisect/good-7ace5bc4fc9f3830e8a067216ce5607df65c9f8c) Initial commit

Temos dois bons commits e o commit atual. Mais uma vez rodamos os testes e vemos que eles falham. Então vamos marcar esse commit como ruim.

git_bisect_example ((no branch)) $ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[4a35ef0f64a686e6301d63736c507f51f2804400] Player 2 wins

Esta é a última revisão para encontrar o commit que introduziu o problema. Rodando os testes de novo vemos que eles passam, então marcamos este commit como bom e o git nos mostra o último commit ruim:

git_bisect_example ((no branch)) $ git bisect good
eda39b0c80b4d66ad2372a0b3302887c0c3486d7 is the first bad commit
commit eda39b0c80b4d66ad2372a0b3302887c0c3486d7
Author: Marcos Brizeno
Date:   Mon Nov 5 21:19:17 2012 -0300

    Refactoring evluator

:040000 040000 595a29074b383565186194f054f8bd2a208fb812 e91bf98032a324f5316f0b4dd5360e3a49a3b30a M      lib

Com isso conseguimos encontrar o commit que introduziu o erro. Para finalizar o git bisect e voltar para onde o master aponta, basta executar:

git_bisect_example ((no branch)) $ git bisect reset

Com isso podemos dar um git show e analisar o commit que quebrou os testes:

git_bisect_example (master) $ git show eda39b0c80b4d66ad2372a0b3302887c0c3486d7

Automatizando

Uma opção ao executar um bisect é passar um commando que será executado automaticamente a cada checkout do git. Dessa forma ele faz o trabalho por nós e apenas nos diz o commit que introduziu o erro. Basta apenas iniciar o bisect e apontar um commit bom e um ruim. Depois passamos o comando utilizar o git bisect run:

git_bisect_example (master) $ git bisect start
git_bisect_example (master) $ git bisect good 7ace5bc
git_bisect_example (master) $ git bisect bad master
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[63735b13802427f9326b3469a41607849335bce6] Refactoring evaluate
git_bisect_example ((no branch)) $ git bisect run rspec
running rspec

Esse comando vai executar o rspec entre cada checkout. Após algum tempo o git vai nos mostrar o commit que introduziu o problema, da mesma forma que foi encontrado antes.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s