Come usare git bisect?


438

Ho letto alcuni articoli dicendo che git bisectè fantastico. Tuttavia, non sono un madrelingua e non riesco a capire perché sia ​​fantastico.

Qualcuno potrebbe dimostrare con un esempio di codice:

  1. Come usarlo?
  2. È proprio come svn blame?

@ 01: Come dice il libro git: esegui una ricerca di forza bruta attraverso la storia del progetto .
Devo dire il

7
Non così /// bruto :-), usa la ricerca binaria.
cojocar il

"git blame" è simile a "svn blame". "git bisect" è una cosa completamente diversa
William Pursell,

Per quello che vale, c'è una buona descrizione di bisect anche in Pro Git . La risposta di Sylvain è un'altra buona idea. Se dopo aver visto tutto ciò che ancora non capisci, ti suggerirei di porre una domanda più specifica. Le domande generali generano risposte generali.
Cascabel,

Risposte:


655

L'idea alla base git bisectè quella di eseguire una ricerca binaria nella storia per trovare una regressione particolare. Immagina di avere la seguente storia di sviluppo:

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

Sai che il tuo programma non funziona correttamente durante la currentrevisione e che funzionava durante la revisione 0. Così la regressione è stato probabilmente introdotto in uno dei commit 1, 2, 3, 4, 5, current.

Potresti provare a controllare ogni commit, crearlo, verificare se la regressione è presente o meno. Se è presente un numero elevato di commit, questo può richiedere molto tempo. Questa è una ricerca lineare. Possiamo fare di meglio facendo una ricerca binaria. Questo è ciò che fa il git bisectcomando. Ad ogni passaggio tenta di ridurre della metà il numero di revisioni potenzialmente dannose.

Utilizzerai il comando in questo modo:

$ 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

Dopo questo comando, giteseguirà il checkout di un commit. Nel nostro caso, sarà commesso 3. Devi creare il tuo programma e verificare se la regressione è presente o meno. Dovrai inoltre indicare gitlo stato di questa revisione con git bisect badse è presente la regressione o git bisect goodse non lo è.

Supponiamo che la regressione sia stata introdotta in commit 4. Quindi la regressione non è presente in questa revisione e lo diciamo a git.

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

Verificherà quindi un altro commit. O 4o 5(in quanto vi sono solo due commit). Supponiamo che sia stato scelto 5. Dopo una build testiamo il programma e vediamo che la regressione è presente. Quindi lo diciamo a git:

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

Ci prova l'ultima revisione, 4. E poiché è quello che ha introdotto la regressione, lo diciamo a git:

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

In questa semplice situazione, abbiamo solo dovuto prova 3 versioni ( 3, 4, 5) invece di 4 ( 1, 2, 3, 4). Questa è una piccola vittoria, ma è perché la nostra storia è così piccola. Se l'intervallo di ricerca è di N commit, dovremmo aspettarci di testare 1 + log2 N commit git bisectinvece di circa N / 2 commit con una ricerca lineare.

Una volta trovato il commit che ha introdotto la regressione, puoi studiarlo per trovare il problema. Una volta fatto, si usa git bisect resetper riportare tutto allo stato originale prima di usare il git bisectcomando.


5
Contraro qui, questa è una buona spiegazione del bisect ma non mi aiuta davvero ad usarlo. Soprattutto da quando sono riuscito a trovare un buon impegno e ora sono su quel ramo. Da questa posizione questa spiegazione non è affatto d'aiuto. Come posso specificare il ramo danneggiato senza verificarlo, ad esempio
PandaWood

4
È possibile utilizzare git bisect bad <rev> [<rev>...]per contrassegnare revisioni specifiche come non valide (o valide git bisect good <rev> [<rev>...]). revpuò essere qualsiasi identificatore di revisione come un nome di ramo, un tag, un hash di commit (o prefisso univoco di hash di commit), ...
Sylvain Defresne,

39
... e una volta git bisect reset
terminato

17
Una delle risposte più fantastiche in pila. Molto ben articolato. Ho fatto questo processo manualmente per anni, solo per scegliere un punto arbitrario a metà strada tra un impegno buono e uno cattivo, poi di nuovo tra quello e il buono / cattivo a seconda che fosse buono / cattivo stesso. È sempre stato un grande dolore nel culo e non avevo mai sentito parlare di questo sottocomando git fino ad oggi ... hahaha
Chev,

3
@Nemoden, sì, può, in pratica può essere utile per qualsiasi tipo di progetto. Devi solo sostituire il passaggio "make test" con "distribuire il sito Web e riprodurre il problema"
alex.b

159

git bisect run bisect automatico

Se si dispone di uno ./testscript automatizzato con stato di uscita 0 se il test è OK, è possibile trovare automaticamente il bug con 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

Ciò presuppone ovviamente che se lo script di test ./testviene rintracciato git, che non scompare in alcuni commit precedenti durante la bisection.

Ho scoperto che molto spesso puoi scappare semplicemente copiando lo script in-tree fuori dall'albero, e possibilmente giocando con PATHvariabili simili a, ed eseguendolo invece da lì.

Ovviamente, se l'infrastruttura di test da cui testdipende si interrompe su commit precedenti, allora non c'è soluzione e dovrai fare le cose manualmente, decidendo come testare i commit uno per uno.

Ho scoperto tuttavia che l'utilizzo di questa automazione spesso funziona e può essere un enorme risparmio di tempo per i test più lenti che si trovano nel tuo backlog di attività, in cui puoi semplicemente lasciarlo funzionare durante la notte e possibilmente avere il tuo bug identificato entro la mattina successiva, vale la pena il tentativo.

Più suggerimenti

Rimani sul primo commit non riuscito dopo bisect invece di tornare a master:

git bisect reset HEAD

start+ iniziale bade goodin una volta sola:

git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~

equivale a:

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

Guarda cosa è stato testato finora (da manuale goode bado run):

git bisect log

Uscita campione:

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

Mostra riferimenti positivi e negativi sul registro git per ottenere una migliore nozione del tempo:

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

Questo mostra solo i commit con un riferimento corrispondente, che riduce il rumore in modo radicale, ma include riferimenti generati automaticamente:

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

che ci dicono quali impegni abbiamo contrassegnato come buoni o cattivi.

Considera questo repository di prova se vuoi giocare con il comando.

Il fallimento è veloce, il successo è lento

Qualche volta:

  • il fallimento avviene rapidamente, ad esempio uno dei primi test si interrompe
  • il successo richiede un po 'di tempo, ad esempio il superamento dei test falliti e tutti gli altri test a cui non ci interessa seguiranno

In questi casi, ad esempio supponendo che l'errore si verifichi sempre entro 5 secondi, e se siamo pigri a rendere il test più specifico come dovremmo, possiamo usare timeoutcome in:

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

Funziona da quando timeoutesce 124mentre il fallimento delle test-commanduscite 1.

Stati di uscita magici

git bisect run è un po 'esigente riguardo agli stati di uscita:

  • qualcosa sopra 127 fa fallire la bisection con qualcosa del tipo:

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

    In particolare, una C assert(0)porta a a SIGABRTed esce con lo stato 134, molto fastidioso.

  • 125 è magico e fa saltare la corsa git bisect skip.

    L'intenzione è quella di aiutare a saltare le build rotte a causa di motivi non correlati.

Vedi man git-bisectper i dettagli.

Quindi potresti voler usare qualcosa come:

#!/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"

Testato su git 2.16.1.


7
Come fa git a evitare che il nuovo test scompaia quando si ripristina / taglia in due una revisione precedente / errata (che non aveva il nuovo test scritto)?
thebjorn,

8
@thebjorn hai un punto: per quanto ne so, il test deve essere su un eseguibile esterno in PATH, o in un file non tracciato nel repository. In molti casi ciò è possibile: collocare il test su un file separato, includere la piastra di prova necessaria con una test_scriptsuite di test ben costruita + modulare ed eseguirlo dal file separato mentre si esegue la bisecatura. Una volta risolto, unisci il test nella suite di test principale.
Ciro Santilli 21 冠状 病 六四 事件 法轮功

1
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视 Mi dispiace, ho ripristinato la tua modifica di seguito, perché sento che è più esplicativo per i neofiti e sta solo aggiungendo un ulteriore punto alla tua risposta (non una risposta esatta come la tua - quindi menzionandolo, è per aggiungere un punto)
Nicks

1
Esistono molti modi per sbagliare con 'git bisect run' - ad esempio vedere come un buon commit è stato annullato da una cattiva unione. Entra, esce, torna dentro e fuori di nuovo e solo l'ultimo "fuori" è male. Tuttavia, puoi sempre fare un 'git bisect' manuale. Perché è una bisect, richiede solo pochi passaggi, ad esempio 1024 commit in 10 passaggi.
combinatore

1
@combinatorist hai ragione che potrebbe fallire. Trovo bisect runparticolarmente utile quando il test impiega molto tempo per terminare e sono abbastanza sicuro che il sistema di test non si romperà. In questo modo, posso semplicemente lasciarlo in esecuzione sullo sfondo, o durante la notte se occupa troppe risorse, senza perdere il tempo di cambiare il contesto del cervello.
Ciro Santilli 11 冠状 病 六四 事件 法轮功

124

TL; DR

Inizio:

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

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

Ripetere:

Il problema esiste ancora?

  • Sì: $ git bisect bad
  • No: $ git bisect good

Risultato:

<abcdef> is the first bad commit

Quando fatto:

git bisect reset

3
Assicurati di essere alla radice del tuo repository git, o otterrai uno strano "Devi eseguire questo comando dal livello superiore dell'albero di lavoro." errore.
PR Whitehead,

Quindi mi sono comportato male sul HEAD e mi sono comportato bene al primo commit, quando l'errore non era presente. Quindi cosa fare dopo? quando bug non è presente git bisect buono per passare al prossimo commit?
Gobliins,

@Gobliins Quando il bug non è presente, correggere, git bisect goodper passare al commit successivo.
Geoffrey Hale,

40

Solo per aggiungere un ulteriore punto:

Possiamo specificare un nome file o un percorso git bisect startnel caso in cui sappiamo che il bug proviene da file particolari. Ad esempio, supponiamo di sapere che le modifiche che hanno causato la regressione erano nella directory com / workingDir, quindi possiamo eseguirle. git bisect start com/workingDirCiò significa che verranno controllati solo i commit che hanno cambiato il contenuto di questa directory e ciò rende le cose ancora più veloci.

Inoltre, se è difficile stabilire se un determinato commit è positivo o negativo, è possibile eseguire git bisect skip, il che lo ignorerà. Dato che ci sono abbastanza altri commit, git bisect ne userà un altro per restringere la ricerca.


15

$ git bisect ..basicamente uno strumento Git per il debug . Esegue il debug di "Git Bisect" eseguendo i commit precedenti dall'ultimo (noto) commit funzionante. Usa la ricerca binaria per passare attraverso tutti quei commit, per arrivare a quello che ha introdotto la regressione / bug.

$ git bisect start # Avvio bisect

$ git bisect bad # affermando che il commit corrente (v1.5) ha il punto di regressione / impostazione 'cattivo'

$ git bisect good v1.0 # menzionandolo l'ultimo buon impegno di lavoro (senza regressione)

Questa menzione di punti "cattivi" e "buoni" aiuterà git bisect (ricerca binaria) a scegliere l'elemento centrale (commit v1.3). Se la regressione è presente su commit v1.3, la imposterai come il nuovo punto 'cattivo', ovvero ( Buono -> v1.0 e Cattivo -> v1.3 )

$ git bisect bad

o allo stesso modo se il commit v1.3 è privo di bug, lo imposterai come il nuovo 'buon punto', ovvero (* buono -> v1.3 e cattivo -> v1.6).

$ git bisect good

2

Nota: i termini goode badnon sono i soli che è possibile utilizzare per contrassegnare un commit con o senza una determinata proprietà.

Git 2.7 (Q4 2015) ha introdotto nuove git bisectopzioni.

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

Con l'aggiunta della documentazione:

A volte non stai cercando il commit che ha introdotto una rottura, ma piuttosto un commit che ha causato un cambiamento tra qualche altro stato "vecchio" e "nuovo" .

Ad esempio, potresti cercare il commit che ha introdotto una correzione particolare.
Oppure potresti cercare il primo commit in cui i nomi dei file del codice sorgente sono stati finalmente convertiti nello standard di denominazione della tua azienda. O qualunque cosa.

In questi casi può essere molto confuso usare i termini "buono" e "cattivo" per riferirsi a "lo stato prima della modifica" e "lo stato dopo la modifica".

Quindi, invece, puoi usare i termini " old" e " new", rispettivamente, al posto di " good" e " bad".
(Ma nota che non puoi mescolare " good" e " bad" con " old" e " new" in una singola sessione.)

In questo uso più generale, si fornisce git bisectun " new" commit con alcune proprietà e un " old" commit che non ha quella proprietà.

Ogni volta che si git bisectverifica un commit, si verifica se quel commit ha la proprietà:
In tal caso, contrassegnare il commit come " new"; altrimenti, contrassegnalo come " old".

Al termine della sezione, git bisectsegnalerà quale commit ha introdotto la proprietà.


Vedi commit 06e6a74 , commit 21b55e3 , commit fe67687 (29 giu 2015) di Matthieu Moy ( moy) .
Vedi commit 21e5cfd (29 giu 2015) di Antoine Delaite ( CanardChouChinois) .
(Unito da Junio ​​C Hamano - gitster- in commit 22dd6eb , 05 ott 2015)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.