Como usar o git bisect?


438

Eu li alguns artigos dizendo que isso git bisecté incrível. No entanto, eu não sou um falante nativo e não consigo entender por que é incrível.

Alguém poderia demonstrar com algum exemplo de código:

  1. Como usá-lo?
  2. É como svn blame?

@ 01: Como o livro do git diz: faça uma pesquisa de força bruta através da história do projeto .
— Eckes

7
Não é assim /// brute :-), usa pesquisa binária.
— Cojocar

"git blame" é semelhante a "svn blame". "git bisect" é uma coisa completamente diferente
— William Pursell

Pelo que vale, também há uma boa descrição de bisect no Pro Git . A resposta de Sylvain é outra boa chance. Se, depois de analisar tudo isso, você ainda não entender, sugiro que faça uma pergunta mais específica. Perguntas gerais geram respostas gerais.
— Cascabel

Respostas:


655

A idéia por trás disso git bisecté realizar uma pesquisa binária no histórico para encontrar uma regressão específica. Imagine que você tem o seguinte histórico de desenvolvimento:

... --- 0 --- 1 --- 2 --- 3 --- 4* --- 5 --- current

Você sabe que seu programa não está funcionando corretamente na currentrevisão e que estava funcionando na revisão 0. Assim, a regressão foi provavelmente introduzido em um dos commits 1, 2, 3, 4, 5, current.

Você pode tentar verificar cada commit, compilar, verificar se a regressão está presente ou não. Se houver um grande número de confirmações, isso pode levar um longo tempo. Esta é uma pesquisa linear. Podemos fazer melhor fazendo uma pesquisa binária. É isso que o git bisectcomando faz. Em cada etapa, ele tenta reduzir pela metade o número de revisões potencialmente ruins.

Você usará o comando assim:

$ git stash save
$ git bisect start
$ git bisect bad
$ git bisect good 0
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[< ... sha ... >] 3

Após este comando, gitfará o checkout de uma confirmação. No nosso caso, será confirmado 3. Você precisa criar seu programa e verificar se a regressão está presente ou não. Você também precisará informar gito status dessa revisão git bisect badse a regressão estiver presente ou git bisect goodse não estiver.

Vamos supor que a regressão foi introduzida no commit 4. Então a regressão não está presente nesta revisão, e dizemos a ela git.

$ make
$ make test
... ... ...
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[< ... sha ... >] 5

Ele fará o checkout de outro commit. De qualquer 4ou 5(como há apenas dois commits). Vamos supor que escolheu 5. Após uma construção, testamos o programa e vemos que a regressão está presente. Em seguida, dizemos para git:

$ make
$ make test
... ... ...
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[< ... sha ... >] 4

Testamos a última revisão 4,. E como é a pessoa que introduziu a regressão, dizemos a ela git:

$ make
$ make test
... ... ...
$ git bisect bad
< ... sha ... > is the first bad commit
< ... commit message ... >

Nesta situação simples, só tivemos de teste 3 versões ( 3, 4, 5) em vez de 4 ( 1, 2, 3, 4). Esta é uma pequena vitória, mas é porque nossa história é muito pequena. Se o intervalo de pesquisa for de N confirmações, devemos esperar 1 + log2 N confirma com, em git bisectvez de aproximadamente N / 2 confirma com uma pesquisa linear.

Depois de encontrar o commit que introduziu a regressão, você pode estudá-lo para encontrar o problema. Feito isso, você git bisect resetcoloca tudo de volta ao estado original antes de usar o git bisectcomando.


5
Vou contrariar aqui, esta é uma boa explicação sobre o bisset, mas realmente não me ajuda a usá-lo. Especialmente desde que eu consegui encontrar um bom commit e estou nesse ramo agora. Nesta posição, essa explicação não ajuda em nada. Como posso especificar o mau ramo sem verificá-lo para fora, por exemplo
— PandaWood

4
Você pode usar git bisect bad <rev> [<rev>...]para marcar revisões específicas como ruins (ou boas git bisect good <rev> [<rev>...]). revpode ser qualquer identificador de revisão como um nome de filial, uma marca, um commit de hash (ou prefixo único de cometer de hash), ...
— Sylvain Defresne

39
... e quando tiver terminado, você digita git bisect resetpara colocar tudo de volta no commit recente
— peetonn

17
Uma das respostas mais impressionantes da pilha. Muito bem articulado. Eu venho fazendo esse processo manualmente há anos, apenas escolhendo um ponto intermediário arbitrário entre um commit bom e ruim, e novamente entre esse e o good / bad dependendo de se foi bom / ruim em si. Tem sido sempre uma enorme dor na bunda e eu nunca tinha sequer ouvido falar deste subcomando git até hoje ... hahaha
— Chev

3
@ Nemoden, sim, pode, basicamente, pode ser útil para qualquer tipo de projeto. Você só precisa substituir a etapa "fazer teste" por "implantar o site e reproduzir o problema"
— alex.b

158

git bisect run bissecção automática

Se você tiver um ./testscript automatizado com o status de saída 0, se o teste for bom, poderá encontrar automaticamente o erro com bisect run:

git checkout KNOWN_BAD_COMMIT
git bisect start

# Confirm that our test script is correct, and fails on the bad commit.
./test
# Should output != 0.
echo $?
# Tell Git that the current commit is bad.
git bisect bad

# Same for a known good commit in the past.
git checkout KNOWN_GOOD_COMMIT
./test
# Should output 0.
echo $?
# After this, git automatically checks out to the commit
# in the middle of KNOWN_BAD_COMMIT and KNOWN_GOOD_COMMIT.
git bisect good

# Bisect automatically all the way to the first bad or last good rev.
git bisect run ./test

# End the bisect operation and checkout to master again.
git bisect reset

Isso supõe, é claro, que, se o script de teste ./testfor rastreado pelo git, ele não desaparecerá em algum commit anterior durante a bissecção.

Descobri que muitas vezes você pode se safar apenas copiando o script in-tree da árvore e, possivelmente, jogando com PATHvariáveis ​​-like, e executando-o a partir daí.

Obviamente, se a infraestrutura de teste da qual testdepende as confirmações mais antigas, não há solução e você terá que fazer as coisas manualmente, decidindo como testar as confirmações uma a uma.

Descobri, no entanto, que o uso dessa automação geralmente funciona e pode economizar muito tempo em testes mais lentos em sua lista de tarefas, em que você pode deixá-lo executar da noite para o dia e possivelmente ter seu bug identificado na manhã seguinte, vale a pena. a tentativa.

Mais dicas

Permaneça no primeiro commit com falha após a divisão em vez de voltar para master:

git bisect reset HEAD

start+ inicial bade goodde uma só vez:

git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~

é o mesmo que:

git checkout KNOWN_BAD_COMMIT
git bisect start
git bisect bad
git bisect good KNOWN_GOOD_COMMIT

Veja o que foi testado até agora (por manual goode badou run):

git bisect log

Saída de amostra:

git bisect log
git bisect start
# bad: [00b9fcdbe7e7d2579f212b51342f4d605e53253d] 9
git bisect bad 00b9fcdbe7e7d2579f212b51342f4d605e53253d
# good: [db7ec3d602db2d994fe981c0da55b7b85ca62566] 0
git bisect good db7ec3d602db2d994fe981c0da55b7b85ca62566
# good: [2461cd8ce8d3d1367ddb036c8f715c7b896397a5] 4
git bisect good 2461cd8ce8d3d1367ddb036c8f715c7b896397a5
# good: [8fbab5a3b44fd469a2da3830dac5c4c1358a87a0] 6
git bisect good 8fbab5a3b44fd469a2da3830dac5c4c1358a87a0
# bad: [dd2c05e71c246f9bcbd2fbe81deabf826c54be23] 8
git bisect bad dd2c05e71c246f9bcbd2fbe81deabf826c54be23
# bad: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05] 7
git bisect bad c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05
# first bad commit: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c0

Mostre boas e más referências no git log para obter uma noção melhor do tempo:

git log --decorate --pretty=fuller --simplify-by-decoration master

Isso mostra apenas confirmações com uma referência correspondente, o que reduz muito o ruído, mas inclui referências do tipo geradas automaticamente:

refs/bisect/good*
refs/bisect/bad*

que nos dizem quais confirmações marcamos como boas ou ruins.

Considere este repositório de teste se quiser brincar com o comando.

O fracasso é rápido, o sucesso é lento

As vezes:

  • a falha ocorre rapidamente, por exemplo, um dos primeiros testes
  • o sucesso leva um tempo, por exemplo, o teste quebrado passa e todos os outros testes com os quais não nos importamos seguem

Nesses casos, por exemplo, supondo que a falha sempre ocorra em 5 segundos, e se tivermos preguiça de tornar o teste mais específico como realmente deveríamos, podemos usar timeoutcomo em:

#!/usr/bin/env bash
timeout 5 test-command
if [ $? -eq 1 ]; then
  exit 1
fi

Isso funciona desde que timeoutsai 124enquanto a falha de test-commandsaídas 1.

Status de saída mágica

git bisect run é um pouco exigente quanto aos status de saída:

  • qualquer coisa acima de 127 faz com que a bissecção falhe com algo como:

    git bisect run failed:
    exit code 134 from '../test -aa' is < 0 or >= 128
    

    Em particular, um C assert(0)leva a SIGABRTe sai com o status 134, muito irritante.

  • 125 é mágico e faz com que a corrida seja pulada git bisect skip.

    A intenção disso é ajudar a pular compilações quebradas devido a razões não relacionadas.

Veja man git-bisectpara os detalhes.

Então você pode querer usar algo como:

#!/usr/bin/env bash
set -eu
./build
status=0
./actual-test-command || status=$?
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
  status=1
fi
exit "$status"

Testado no git 2.16.1.


7
Como o git sabe para evitar que seu novo teste desapareça ao reverter / dividir novamente em uma revisão anterior / ruim (que não teve seu teste recém-escrito)?
— thebjorn

8
@thebjorn você tem razão: até onde eu sei, o teste deve estar em um executável externo no PATH ou em um arquivo não rastreado no repositório. Em muitos casos, isso é possível: coloque o teste em um arquivo separado, inclua o clichê de teste necessário com um test_scriptconjunto de testes modular + bem criado e execute-o a partir do arquivo separado durante a divisão. Ao corrigir, mescle o teste no conjunto de testes principal.
— Ciro Santilli escreveu:

1
@CiroSantilli 视 事件 法轮功 纳米比亚 威 视 Desculpe, eu revirei sua edição abaixo, porque sinto que é mais explicativo para iniciantes e está apenas adicionando mais um ponto à sua resposta (não uma resposta exata como a sua - por mencionar isso, sua para adicionar um ponto)
— Nicks

1
Existem várias maneiras de dar errado com o 'git bisect run' - por exemplo, ver como um bom commit foi revertido por uma má combinação. Ele entra, sai, entra e sai novamente e apenas o último "sair" é ruim. No entanto, você sempre pode fazer um 'git bisect' manual. Por ser uma bissetriz, são necessárias apenas algumas etapas - por exemplo, 1024 confirma em 10 etapas.
— combinatorist

1
@combinatorist, você está certo de que pode falhar. Acho bisect runespecialmente útil quando o teste demora muito para terminar e tenho certeza de que o sistema de teste não será interrompido. Dessa forma, posso deixá-lo em execução em segundo plano, ou da noite para o dia, se consumir muitos recursos, sem perder o tempo de troca de contexto do cérebro.
— Ciro Santilli # 11/18

124

TL; DR

Começar:

$ git bisect start
$ git bisect bad
$ git bisect good <goodcommit>

Bisecting: X revisions left to test after this (roughly Y steps)

Repetir:

O problema ainda existe?

  • Sim: $ git bisect bad
  • Não: $ git bisect good

Resultado:

<abcdef> is the first bad commit

Quando concluído:

git bisect reset

3
Certifique-se de estar na raiz do seu repositório git ou obterá um estranho "Você precisa executar este comando no nível superior da árvore de trabalho". erro.
— PR Whitehead

Então, fiz git bad no meu HEAD e git good no primeiro commit, quando o erro não estava presente. Então, o que fazer em seguida? quando o bug não está presente git bisect good para passar para o próximo commit?
— Gobliins

@ Gobliins Quando o bug não está presente, corrija git bisect goodpara passar para a próxima confirmação.
— Geoffrey Hale

40

Apenas para adicionar mais um ponto:

Podemos especificar um nome de arquivo ou caminho para o git bisect startcaso de sabermos que o bug veio de arquivos específicos. Por exemplo, suponha que soubéssemos que as mudanças que causaram a regressão estavam no diretório com / workingDir, podemos executar. git bisect start com/workingDirIsso significa que apenas as confirmações que alteraram o conteúdo desse diretório serão verificadas e isso tornará as coisas ainda mais rápidas.

Além disso, se for difícil dizer se um commit específico é bom ou ruim, você pode executar git bisect skip, o que o ignorará. Dado que existem outras confirmações suficientes, o git bisect usará outra para restringir a pesquisa.


15

$ git bisect ..basicamente uma ferramenta Git para depuração . O 'Git Bisect' debuga ao passar pelas confirmações anteriores desde a sua última confirmação de trabalho (conhecida). Ele usa pesquisa binária para passar por todos esses commits, para chegar ao que introduziu a regressão / bug.

$ git bisect start # Iniciando a divisão

$ git bisect bad # afirmando que o commit atual (v1.5) tem o ponto de regressão / configuração 'ruim'

$ git bisect good v1.0 # mencionar o último bom commit de trabalho (sem regressão)

Essa menção de pontos 'ruins' e 'bons' ajudará o git bisect (pesquisa binária) a escolher o elemento do meio (commit v1.3). Se a regressão estiver presente na confirmação v1.3, você a definirá como o novo ponto 'ruim' (ie - Bom -> v1.0 e Ruim -> v1.3 )

$ git bisect bad

ou da mesma forma, se o commit v1.3 estiver livre de erros, você o definirá como o novo 'ponto positivo', ou seja (* Bom -> v1.3 e Ruim -> v1.6)

$ git bisect good

2

Nota: os termos goode badnão são os únicos que você pode usar para marcar um commit com ou sem uma determinada propriedade.

O Git 2.7 (quarto trimestre de 2015) introduziu novas git bisectopções.

 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
                  [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]

Com a adição de documentação:

Às vezes, você não está procurando a confirmação que introduziu uma quebra, mas sim uma confirmação que causou uma alteração entre algum outro estado "antigo" e "novo" .

Por exemplo, você pode estar procurando a confirmação que introduziu uma correção específica.
Ou você pode estar procurando o primeiro commit no qual os nomes de arquivos do código-fonte foram finalmente convertidos no padrão de nomes da sua empresa. Como queiras.

Nesses casos, pode ser muito confuso usar os termos "bom" e "ruim" para se referir a "o estado antes da mudança" e "o estado após a mudança".

Portanto, você pode usar os termos " old" e " new", respectivamente, no lugar de " good" e " bad".
(Mas observe que você não pode misturar " good" e " bad" com " old" e " new" em uma única sessão.)

Nesse uso mais geral, você fornece git bisectum " new" commit com alguma propriedade e um " old" commit que não possui essa propriedade.

Sempre que git bisectfizer check-out de um commit, você testa se esse commit possui a propriedade:
Se houver, marque o commit como " new"; caso contrário, marque-o como " old".

Quando a bisseção for concluída, git bisectinformará qual confirmação introduziu a propriedade.


Consulte commit 06e6a74 , commit 21b55e3 , commit fe67687 (29 de junho de 2015) por Matthieu Moy ( moy) .
Veja commit 21e5cfd (29 de junho de 2015) por Antoine Delaite ( CanardChouChinois) .
(Mesclado por Junio ​​C Hamano - gitster- na confirmação 22dd6eb , 05 de outubro de 2015)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.