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?
IAdapter
fonte
@ 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.

Sylvain Defresne
fonte
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.

Ciro Santilli adicionou uma nova foto
fonte
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
Geoffrey Hale
fonte
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.

Nicks
fonte
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
Nabeel Ahmed
fonte
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)

VonC
fonte