Git como ferramenta de debug

20 minuto(s) de leitura

Tem certeza? Fazer debug com Git

Quais s√£o as ferramentas que v√™m √† sua cabe√ßa quando algu√©m diz ‚Äúdebug‚ÄĚ? Deixa eu tentar adivinhar:

  • um detector de vazamento de mem√≥ria (ex: Valgrind);
  • um profiler (ex: GNU gprof);
  • uma fun√ß√£o que para o seu programa e te d√° um REPL (ex: a fun√ß√£o breakpoint do Python ou o byebug do Ruby);
  • alguma coisa que √© de fato chamada depurador (ou ‚Äúdebugger‚ÄĚ), como o GDB, ou algum similar que v√™m nas IDEs;
  • ou ainda a nossa velha amiga, a fun√ß√£o print.

Certo, ent√£o neste texto eu tentarei te convencer a adicionar o Git a essa lista.

Quando voc√™ est√° versionando algum c√≥digo com o Git, o reposit√≥rio √© uma preciosa fonte de informa√ß√£o. Muitas pessoas apenas pensam no Git s√≥ como aquele comando que em que se faz a sequ√™ncia git add, git commit e git push, da mesma forma como se faria o upload de um arquivo para o Google Drive ou se postaria uma foto no Instagram. Por√©m, como o Git mant√©m todo o hist√≥rico de commits desde o mais antigo, ele √© provavelmente a ferramenta que melhor conhece o c√≥digo. Cada vers√£o de cada arquivo √© armazenada no reposit√≥rio (neste texto estou me referindo por ‚Äúreposit√≥rio‚ÄĚ o reposit√≥rio local, n√£o o do GitHub, GitLab, Bitbucket, etc), e achar informa√ß√£o √ļtil nele √© como um trabalho de arqueologia.

Ent√£o, vou ent√£o mostrar aqui alguns conceitos e ferramentas √ļteis para extrair tudo que voc√™ precisa a partir dele!

Recapitulando conceitos b√°sicos

Antes de seguirmos em frente, vamos primeiro recapitular os conceitos b√°sicos do Git.

Commits s√£o as vers√Ķes de um reposit√≥rio. Eles s√£o snapshots, n√£o deltas, isso significa que um commit n√£o sabe o que foi alterado. Em vez disso, um commit guarda o conte√ļdo de cada arquivo. Quando voc√™ executa git show, voc√™ n√£o est√° olhando o conte√ļdo de um commit, voc√™ na verdade est√° olhando um patch, ou seja, o que mudou em rela√ß√£o ao commit pai. Essa mudan√ßa, por√©m, √© feita de uma forma inteligente que economiza espa√ßo.

Commits têm referências a seu(s) commit(s) pais (se houverem):

  • um commit normal tem um √ļnico pai;
  • um commit de merge tem dois pais;
  • um commit de merge octopus tem dois ou mais pais;
  • um commit inicial n√£o tem pai.

Commits também armazenam quando e quem os criou!

Branches são só referências para commits. A bem da verdade, uma branch é só um arquivo que contém o hash de um commit;

O histórico de commits não é uma linha do tempo linear! Na verdade, é um DAG, um grafo acíclico direcionado, em outras palavras, ele é um conjunto de diferentes linhas do tempo que podem ter um passado em comum (uma bifurcação, ou fork point em inglês) e que podem ter estados onde timelines se encontram (um commit de merge), mas sem nenhum loop!

A √°rea de staging, antigamente chamada de cache (nome ainda usado √†s vezes‚Ķ) e internamente chamada de index √© o lugar onde um commit √© preparado (em outras palavras, √© o lugar para onde voc√™ manda um arquivo quando executa git add). O conte√ļdo da √°rea de staging √© o conte√ļdo do seu √ļltimo commit, al√©m das altera√ß√Ķes que voc√™ fez usando git add (aquelas que s√£o mostradas em verde no git status).

O diretório de trabalho (em inglês, working directory) é o diretório onde estão armazenados no disco os arquivos do seu projeto. Da perspectiva deu quem está vendo ou de quem está escrevendo e executando o código, este pode parecer a principal área do Git (comparado ao histórico de comits e à área de staging). Porém, da perspectiva do Git, este é a menos importante, já que tudo aqui pode ser modificado, apagado, criado, e o Git não irá registrar, a não ser, claro, que você explicitamente diga a ele pra fazer isso (usando git add, por exemplo).

Se você não conhecia alguma coisa dessa seção, surigro fortemente que leia a seção 1.3 do livro Pro Git.

Pathspec e git ls-files

Vamos ao nosso primeiro conceito aqui: o pathspec (eu enrolo a língua pra falar essa palavra…). Um pathspec é uma string que pode ser passada como argumento para vários comandos do Git para especificar arquivos.

Uma forma legal de ver os pathspecs funcionando √© usando git ls-files. Esse comando lista todos os arquivos na √°rea de staging, por√©m, se voc√™ passar um pathspec como par√Ęmetro, ele ir√° listar todos os arquivos que casam com esse pathspec.

O pathspec mais óbvio é o próprio enderço do arquivo. Se você tem um arquivo chamado README.md, então README.md vai ser um pathspec que o representa, e se ele está dentro de um diretório chamado src, um pathspec que irá identificá-lo é src/README.md. Note que, por padrão, pathspecs são relativos ao diretório atual

*

O primeiro superpoder do pathspec que irei mostrar a você é o *. Esse caracter casa com zero ou mais caracteres. Por exemplo, este comando lista todos os arquivos que terminam em .c na área de staging e que estão no diretório atual ou em seus subdiretórios, recursivamente:

git ls-files '*.c'

Talvez voc√™ esteja pensando: ‚Äúnada novo at√© agora, √© apenas uma expans√£o de *‚ÄĚ. Bem, na verdade n√£o. Note que '*.c' est√° entre aspas, isso significa que ele n√£o √© uma string, e o shell n√£o est√° expandindo ele. Em vez disso, quem est√° fazendo a expans√£o √© o pr√≥prio Git.

Mas qual √© a diferen√ßa? Lembra que eu disse ‚Äúno diret√≥rio atual ou em seus subdiret√≥rios‚ÄĚ? Essa √© a diferen√ßa entre o * do pathspec e o do * do shell: nessa situa√ß√£o, o * do shell ir√° casar apenas com arquivos no diret√≥rio atual, enquanto que o pathspec * ir√° casar com todos os arquivos que est√£o no diret√≥rio atual ou em um subdiret√≥rio dele! Dessa forma, se executarmos:

git ls-files *.c

o shell irá substituir *.c por todos os arquivos que estão no diretório atual, então o git ls-files vai listar só eles.

: + palavras m√°gicas

: √© um caracter especial para os pathspecs que √© seguido de uma palavra m√°gina. Eu n√£o irei entrar em detalhes sobre isso, mas existem dois casos que acho muito √ļteis.

O primeiro é o :/. Ele representa a raiz do repositório. Se você está em um subdiretório e quer achar arquivos pelos seus caminhos absolutos (isto é, relativos à raiz do repositório), você terá que usar :/. Por exemplo, :/*.c irá casar com todos os arquivos que terminam em .c no repositório, não importa onde eles estão localizados.

O segundo é :!. Se a gente colocar :! na frente de um pathspec, então o pathspec irá casar com todos os arquivos que não casam com o resto do pathspec. Por exemplo:

git ls-files ':!*.c'

Esse comando ir√° listar todos os arquivos na √°rea de staging que n√£o terminam em .c.

Mais sobre pathspecs e git ls-files

Pathspecs s√£o realmente √ļteis para selecionar arquivos para passar como argumentos para o Git. Voc√™ pode ler mais sobre eles no gloss√°rio do Git (man gitglossary), procurando por ‚Äúpathspec‚ÄĚ.

O git ls-files, que eu usei como exemplo para os pathspecs, é uma ótima ferramenta para encontrar arquivos no repositório. Ele pode substiuir o comando find, já que tem uma sintaxe bem mais simples.

Git Grep

git grep, como o próprio nome diz, é um grep melhorado pelo Git.

E o que isso significa? Bom, lembra que eu disse que o Git é provavelmente a ferramenta que conhece o seu código? git grep se aproveita disso para fazer um grep melhorado.

A sintaxe do git grep é, basicamente, a seguinte:

git grep [<flags>] [<padr√£o>] [<commit>] -- [<pathspec>]

Você pode usar várias flags do grep no git grep, como por exemplo, -E, -P ou -i. O pathspec e o commit são opcionais. O -- é opcional na maior parte dos casos, porém, é recomendado usá-lo para evitar ambiguidades.

Se você passar umn commit, então o Git Grep vai procurar nele o padrão, mas só nele, não vai procurar em outros commits.

Se você não fornecer o pathspec, o Git Grep irá procurar pelo padrão em todos os arquivos no diretório atual ou em seus subdiretórios. Ele é bem mais rápido que o GNU Grep ou o BSD Grep. Você pode ver isso na imagem a seguir, nela eu estou procurando por #include no código fonte do próprio Git, usando BSD Grep, GNU Grep e Git Grep, respectivamente:

Git grep vs BSD grep and GNU grep, em um Macbook Air M1

Mas o que mais o Git Grep pode fazer? Bom, d√™ uma olhada nas flags --heading e --break. --heading agrupa a sa√≠da pelo arquivo onde est√° cada linha, e --break apenas insere uma linha entre os grupos. Isso √© muito √ļtil para procurar, por exemplo, arquivos que chamam uma fun√ß√£o ou usam uma constante.

Git grep com --heading e --break, procurando por looks_like em todos os códigos-fonte C.

Um recurso bem interessante do Git Grep √© a flag -W (ou --show-function). Quando se ela, o Git Grep n√£o s√≥ vai mostrar a linha que cont√©m o que voc√™ est√° procurando, mas vai tamb√©m mostrar toda a fun√ß√£o onde ela est√° localizada. Ent√£o, vamos olhar o mesmo comando que eu mostrei na √ļltima imagem, por√©m, adicionando a flag -W (git grep --heading --break -W 'looks_like' -- '*.c'):

Git grep com -W mostrando a função toda que contém looks_like.

Git Grep √© uma ferramenta incr√≠vel para encontrar c√≥digo. Ok, mas voc√™ deve estar pensando ‚Äúlegal, mas eu consigo fazer algo parecido com isso com a navega√ß√£o de c√≥digo da minha IDE, pulando pra defini√ß√Ķes e para usos‚ÄĚ. Isso √© verdade para v√°rios casos. Por√©m o Git Grep √© realmente √ļtil quando voc√™ quer procurar somente em alguns arquivos (usando um pathspec como restri√ß√£o), quando voc√™ quer procurar por uma regex gen√©rica em vez de um nome de uma vari√°vel ou fun√ß√£o, quando voc√™ quer procurar em outro commit ou quando voc√™ s√≥ n√£o quer abrir uam IDE e prefere procurar a partir do terminal. N√£o √© uma ferramenta que substitui outras, e sim uma ferramenta complementa as outras.

Git Blame

Se voc√™ j√° usou alguma vez o git blame, √© bem prov√°vel que vocŠļĹ esteja esperando que eu fale sobre ele e como ele √© maravilhoso. Se esse √© o caso, ent√£o por favor, n√£o pule esta se√ß√£o porque eu tenho algo realmente importante para te dizer.

Se esse n√£o √© o caso e voc√™ nunca ouviu falar ou nunca usou o git blame, ele √© uma ferramenta que mostra, para cada linha, quem foi a √ļltima pessoa que a mudou, qual foi o commit em que essa altera√ß√£o foi feita, e a data e hora dessa mudan√ßa. Olhe para a imagem a seguir. Eu estou rodando git blame Main.hs, em que Main.hs √© um arquivo que eu escrevi:

Git blame. A primeira coluna mostra os primeiros caracteres do hash do √ļltimo commit que alterou aquela linha, Main.hs √© o nome do arquivo, Lucas Oshiro (tamb√©m conhecido como eu) √© a √ļltima pessoa que alterou essas linhas. Voc√™ tamb√©m pode ver a data e a hora dessa √ļltima mudan√ßa.

Legal, ent√£o, isso me mostra evid√™ncias para que eu possa xingar o c√≥digo de algu√©m que trabalha comigo? Bom, na maior parte das vezes, sim, mas lembre-se: o git blame mostra apenas quem fez a √ļltima modifica√ß√£o. Talvez essa pessoa apenas mudou o nome de uma vari√°vel, aplicou uma mudan√ßa no estilo do c√≥digo, moveu uma declara√ß√£o de uma fun√ß√£o para outro arquivo, ou qualquer outra mudan√ßa que seja na pratica seja quase irrelevante para o funcionamento do c√≥digo. Muitas vezes a pessoa nem sabe o que o c√≥digo faz (por exemplo, talvez a pessoa apenas executou uma ferramenta que formata c√≥digo, sem nem ter lido ele).

Isso tamb√©m se aplica √† outra informa√ß√£o: a data e a hora que √© mostrado √© apenas a √ļltima vez que a linha foi alterada, e n√£o nos diz quando ela foi criada. A mesma coisa vale para o commit: o commit que √© mostrado √© o √ļtilmo commit que alterou alguma coisa na linha, n√£o aquele que alterou alguma coisa √ļtil ou o commit que primeiro adicionou aquela linha.

O Git Blame (e outras ferramentas baseadas nele, como o Annotate nas IDEs da JetBrains, o magit-blame no Emacs, ou o GitLens no VSCode) √© sem d√ļvida bastante √ļtil, por√©m, n√£o √© uma fonte da verdade. Se voc√™ quer saber algo al√©m de apenas o que ocorreu na √ļltima mudan√ßa na linha, ent√£o voc√™ precisar√° de algo mais poderoso‚Ķ

Git Log e seus poderes secretos

git log é um dos mais famosos comandos do Git. Ele é comando que você executa para ver o histórico de commits. Nada novo até aqui. Porém, ele tem alguns recursos menos conhecidos que eu considero como sendo o próximo passo para quando o Git Blame não é o suficiente para suas necessidades.

Passando um pathspec como argumento para o Git Log

Voc√™ pode restringir a sa√≠da do git log passando um pathspec como seu √ļltimo argumento, assim: git log -- <pathspec>. De novo, o -- pode ser omitido na maior parte dos casos, por√©m, √© uma boa pr√°tica mant√™-lo para evitar ambiguidades.

Quando você faz isso, a saída irá conter apenas os commits que introduziram uma mudança nos arquivos que casam com o pathspec, em relação a seus commits anteriores. Olhe esse exemplo:

Histórico de commits.

Se voc√™ quer saber quando alguma coisa foi introduzida, voc√™ pode inspecionar esses commits. Quando voc√™ encontrar em qual commit foi feita essa altera√ß√£o, voc√™ ir√° descobrir quem e quando introduziu o commit. Se a mensagem de commit foi bem escrita e se a mudan√ßa foi at√īmica, voc√™ ainda saber√° porque o commit foi criado (e porque o c√≥digo existe). Se voc√™ est√° usando GitHub, voc√™ tamb√©m poder√° copiar o hash desse commit e procurar o Pull Request que o cont√©m, e isso √© ainda mais informa√ß√£o, j√° que voc√™ pode ler a discuss√£o e a revis√£o de c√≥digo!

Esse recurso do Git Log salvou minha vida várias vezes. Se eu encontrava um trecho de código que era difícil de entender para que servia, em vez de tentar lê-lo, eu usava o Git Log para encontrar o commit que o introduziu e entender o que a pessoa que o escreveu estava tentando fazer, qual o contexto dessa introdução, qual o problema que ela queria resolver, e assim por diante. Apenas tente fazer isso!

A flag -p

Apesar de tudo, pode ser bem entendiante olhar cada commit manualmente. Você pode usar -p para mostrar o patch de cada commit. Em outras palavras, é como executar git show para cada commit que aparece no log:

O mesmo que a √ļltima imagem, por√©m usando a flag -p

A flag -S

A flag -S √© um tesouro perdido no Git Log. Com ele, voc√™ pode ver todos os commits que aumentaram ou diminu√≠ram o n√ļmero de ocorr√™ncias de uma string. Pessoalmente falando, ele praticamente aposenta o Git Blame para mim: mesmo se uma pessoa moveu um trecho de c√≥digo para outro lugar ou para outro arquivo, o git log -S vai conseguir encontrar a introdu√ß√£o dele.

Na imagem a seguir, eu estou usando git log -S para encontrar a primeira introdu√ß√£o de uma string contida no arquivo hardcoded_values.c (como voc√™ pode ver na sa√≠da do primeiro git grep). Depois, note que essa string n√£o foi originalmente introduzida nesse arquivo (como voc√™ pode ver na sa√≠da do segudo git grep). A princ√≠pio, ele era parte do arquivo state_machine.c, ent√£o ele foi movido para outro arquivo. Isso resolve o problema do Git Blame apenas ‚Äúculpar‚ÄĚ a pessoa que moveu aquela linha, em vez da que a criou.

Perd√£o, Git Blame...

Você ainda pode usar -G em vez de -S. Isso permite usar uma expresão regular em vez de uma string.

Git Bisect

O git blame nos diz a √ļltima mudan√ßa de uma linha e o git log -S nos diz quando uma string foi introduzida ou removida. Por√©m, eles s√≥ operam sobre texto. Para v√°rios casos isso √© suficiente, por√©m, √†s vezes voc√™ n√£o quer procurar por mudan√ßas em um texto, mas quer procurar por mudan√ßas no comportamento do programa, como novos bugs ou qualquer outra coisa que n√£o esteja funcionando como esperado.

Nesses casos, o git blame ou o git log -S não serão suficientes, porque você não sabe qual código causou essa mudança de comportamento, e você não sabe exatamente o que procurar. Em projetos complexos, talvez essa mudança foi feita em um lugar que você nunca imaginaria, como por exemplo, em uma classe ou função que você pensou que não fosse relacionada à que quebrou.

E como o Git pode nos ajudar a encontrar essa mudança?

Senhoras e senhores, √© uma honrar apresentar a voc√™s o meu comando favorito do Git: o Git Bisect! Ele nos permite encontrar o commit que quebrou alguma coisa no c√≥digo. Dado um commit ‚Äúbom‚ÄĚ (um commit que n√£o est√° quebrado, criado antes da introdu√ß√£o do bug), e um commit ‚Äúruim‚ÄĚ (um commit em que o c√≥digo certamente est√° quebrado), o Git ir√° fazer uma busca bin√°ria at√© que o commit quebrado seja encontrado.

Uma vez encontrado esse commit, você pode olhá-lo e conseguir todo o tipo de informação que discutimos anteriormente.

O Git Bisect pode ser usado de duas maneiras: uma mais manual, em que ele guia você até que o commit que introduziu o bug seja encontrado, e uma automática, que o Git encontra esse commit pra você.

Um exercício prático

Eu vou demonstrar o Git Bisect usando este reposit√≥rio: https://github.com/lucasoshiro/bisect_demo. Ele √© bem simples, apenas cont√©m um √ļnico arquivo Python, com um c√≥digo bem estranho e dif√≠cil de entender:

#!/usr/bin/env python3

from sys import argv
from math import log

ops = 0x2B2D2F2C2A5E3E5F

def func(a, b):
    return '\n'.join(
        (lambda r: f'{a} {f} {b} = {r}')(eval(f'{a}{f}{b}'))
        for f in ops.to_bytes((int(log(ops, 16)) + 1) // 2, 'big').decode())


if __name__ == '__main__':
    a, b = map(int, argv[1:])
    print(func(a, b))

E o que ele faz? Bom, ele recebe dois n√ļmeros como argumentos, e realiza algumas opera√ß√Ķes usando eles:

calc.py executando.

Ele funciona para a maior parte dos n√ļmeros, exceto se o segundo √© 0. Como uma das opera√ß√Ķes √© a divis√£o e como n√≥s n√£o estamos tratando erros de divis√£o por zero nesse c√≥digo, esta entrada quebra o script:

Ah n√£o...

Como você pode ver, nós sabemos onde o código está quebrando, porém, não conseguimos ver nenhuma operação de divisão nele. Se quisermos corrigir esse código, primeiro precisamos saber o que causou essa divisão, e isso não está claro aqui.

Claro que podemos executar git log aqui e tentar achar um commit que possa ter inserido esse bug. Porém, mesmo executando git log -p -- calc.py não será de grande ajuda. Veja:

Nada √ļtil aqui. A mensagem de commit diz nada sobre o c√≥digo (s√≥ nome de frutas...) e a √ļnica mudan√ßa entre um commit e outro √© um valor hexadecimal.

Hora do Git Bisect nos salvar!

Rodando o Git Bisect manualmente

A primeira coisa que voc√™ precisa fazer √© iniciar a bisse√ß√£o (a busca bin√°ria, ou em ingl√™s bisect), usando o comando git bisect start. Se voc√™ executar git status, ele mostrar√° que a bisse√ß√£o foi iniciada. Alguns shells que mostram informa√ß√Ķes sobre o Git no prompt tamb√©m mostram a bisse√ß√£o foi iniciada. Se voc√™ j√° fez o que precisava com a bisse√ß√£o, ent√£o voc√™ deve executar git bisect reset para termin√°-la.

Iniciando a bisseção

Eu disse que para fazermos a bisse√ß√£o, precisamos de um ‚Äúcommit ruim‚ÄĚ e um ‚Äúcommit bom‚ÄĚ. Neste caso, n√≥s sabemos que o √ļltimo commit √© ruim, j√° que ele quebra quando passamos 0 como segundo par√Ęmetro.

Em um cen√°rio real, provavelmente voc√™ saber√° qual √© um ‚Äúbom‚ÄĚ commit, ele ser√° qualquer commit que cont√©m um c√≥digo que funciona, por exemplo, o commit da √ļltima release que n√£o esteja quebrada. No caso deste exemplo, eu digo a voc√™ que o primeiro commit n√£o tem bugs. Se voc√™ quiser ver isso, fa√ßa um checkout para ele e tente executar calc.py passando 0 como segundo argumento:

Não faz nada, porém, pelo menos não está dividindo por zero.

Dessa forma, agora sabemos um commit bom e um ruim, e nossa situação é esta (os commits estão em ordem cronológica, o primeiro commit é o primeiro):

8959689 Início     <- BOM!
f1a445e banana
e516236 goiaba
0acc414 Laranja
18c911e Toranja
0f3d5c7 Lim√£o
7e27a60 Framboesa
f16e5e7 Morango
c3eb7db Carambola  <- RUIM!

E agora precisamos dizer ao Git Bisect sobre isso. Executamos git bisect good 8959689 para dizer que o commit inicial é bom. Como o commit atual é ruim, então podemos executar apenas git bisect bad para dizer ao Git Bisect isso, mas você poderia também executar git bisect bad c3eb7db, ou passar como argumento qualquer outro commit que você saiba que é ruim.

Depois de executar isso, o Git Bisect vai automaticamente fazer um checkout para o commit que est√° no meio do hist√≥rio entre o bom e o ruim, ou seja, o commit 18c911e (Toranja). Ap√≥s isso, executamos ./calc.py <algum n√ļmero> 0 para decidir se esse commit √© bom ou ruim.

E descobrimos que esse commit é um dos ruins...

Certo, então nossa situação é a seguinte:

8959689 Início     <- BOM!
f1a445e banana
e516236 goiaba
0acc414 Laranja
18c911e Toranja    <- RUIM!
0f3d5c7 Lim√£o
7e27a60 Framboesa
f16e5e7 Morango
c3eb7db Carambola  <- RUIM!

E, novamente, precisamos dizer ao Git Bisect que esse commit é um dos ruins. Então, executamos git bisect bad. Como você deve imaginar, agora o Git Bisect vai fazer um checkout para o commit que está no meio dos commits 8959689 (Início) and 18c911e (Toranja). Esse commit é o e516236 (goiaba).

Agora fazemos a mesma coisa de antes: executamos ./calc.py <something> 0, vemos se ele quebra ou não, se sim então executamos git bisect bad, caso contrário executamos git bisect good, até que a busca binária termine e encontre o commit com problemas. Fazemos isso aqui:

Executando git bisect bad e git bisect good até que a busca binária chegue ao fim.

Finalmente encontramos o commit que introduziu o bug: 0acc414 (Laranja). Agora você pode sair da bisseção com git bisect reset, e o Git irá voltar para o mesmo commit que você estava quando executou git bisect start. Se você está com curiosidade para saber porque essa mudança quebra o código, aqui está uma dica: 0x2F é o valor ASCII do caracter / em hexadecimal.

O Git Bisect é legal, porém, executá-lo manualmente dessa forma (verificando se um commit é bom ou ruim e em seguida executando git bisect bad ou git bisect good) pode ser bem entendiante. Dependendo da situação, é possível fazer isso de forma automática usando git bisect run!

Rodando o Git Bisect automaticamente

Caso exista um comando que diga se um commit √© bom ou ruim, ent√£o o Git Bisect pode fazer a busca bin√°ria automaticamente! Esse comando pode ser qualquer coisa, como um shellscript, um script Python, um execut√°vel, um teste, entre v√°rias outras coisas. O √ļnico requisito √© que seu status code deve seguir algumas regras:

  • Se o commit √© bom, ent√£o o comando dever√° devolver 0;
  • Se o commit √© ruim, ent√£o o comando dever√° devolver qualquer coisa entre 1 e 127, inclusivamente, exceto 125;
  • Se n√£o √© poss√≠vel decidir se um commit √© ruim ou bom, ent√£o ele precisa ser ignorado, e para isso o dever√° devolver 125.

Neste exemplo, estamos verificando se o c√≥digo lan√ßa uma exce√ß√£o. Por padr√£o, quando um c√≥digo termina sua execu√ß√£o em um exce√ß√£o em Python, ent√£o seu status code √© 1, ou √© 0 quando a execu√ß√£o termina sem problemas. Ent√£o, apenas executar ./calc.py <algum n√ļmero> 0 √© suficiente, j√° que ele ir√° devolver 0 quando tudo terminou sem problemas e 1 quando um bug ocorreu. Por√©m, lembre-se de que esse n√£o √© sempre o caso, e dependendo √© poss√≠vel que voc√™ precise escrever um script de teste para tomar essa decis√£o.

Nós começamos a bisseção da mesma forma que antes:

git bisect start
git bisect good <hash do commit bom>
git bisect bad

Como queremos fazer a bisseção automática usando como critério ./calc.py 14 0, então executamos git bisect run ./calc.py 14 0. Funciona como mágica:

Sim, isso mesmo: é um comando que encontra o bug para você!

Depois disso, você também precisa executar git bisect reset para terminar a bisseção. E é isso. Não é legal?

Conclus√£o

Esses comandos me ajudaram muito quando eu navegava em bases de código bem grandes e preciava encontrar as causas bugs. Mas não apenas isso, eles podem ajudar a entender o código, por eles serem, essencialmente, ferramentas de busca. Simples e flexíveis, mas incrivelmente poderosas.

Obrigado pela leitura, se você encontrou algo errado ou tem alguma sugestão, abra uma issue no meu GitHub.

Atualizado em: