Git pozna bisekcijo, kar lahko precej pomaga pri iskanju izvora hroščev. Sledi predstavitev na čisto realnem primeru:
$ git bisect start # začnem z bisekcijo
$ git bisect bad # trenutno stanje (commit, revision) je slabo
$ man git-bisect # kako že naprej? 
$ git log # poiščem stanje za katero vem da je dobro (po spominu, drugače bi moral testirati)
commit 41be597523c753415deac2a4ad131bc5119831c0
…
…
…
$ git bisect good f0509a2be # označim dobro stanje
Bisecting: 11 revisions left to test after this
[6e6d793a3929575752793e9cfc222a0e4443dc78] lib*: removed trailing whitespace
Iz izpisa sledi, da je med dobrim in slabim stanjem 2*11±1 drugih stanj. Zdaj sem na sredi začetnega intervala in lahko preverim ali je stanje dobro. Če je res dobro, je hrošč nastal nekje v zgornji, drugače pa v spodnji polovici intervala. Postopek bom ponavljal dokler ne izluščim problematičnega stanja. Ključno je da je preverjanje stanja stanja zanesljivo, drugače lahko začne git iskati krivca med samimi nedolžnimi!
To testiranje seveda lahko opravimo na roke, lahko si pa pomagamo s skripto. Ker moj primer ni trivialen se mi je splačalo testiranje zavtomatizirati (pa čeprav je ta bisekcija zelo kratka). Git zna pognati skripto in za ta postopek je pomembna le njena izhodna vrednost (return code, exit status) - 0 pomeni da je stanje dobro, 1 pa da ni. Za še bolj zanesljive rezultate bi lahko v skripti test pognal večkrat in potrdil uspeh le če so bile vse ponovitve uspešne.
Testna skripta sicer zgleda kriptično, njen namen je pa da preveri koliko časa traja testni postopek. Če traja predolgo je stanje slabo …
$ cat lock-timer
test=”cast -c xine-lib syslog-ng sudo shared-mime-info poppler-data nas”
critical_time=10018000 # 1 + 0 minutes + 0 for m + 18.000 seconds
time=$(sudo su -c “./install &>/dev/null;
sed -i ’s,\”y\”,n,’ /usr/sbin/cast;
{ time $test >/dev/null; } 2>&1 | sed -n ‘/real/{ s,[reals\t\.],,g; s,m,0,p }’”)
# ^ time it and intelligently strip all non-numerals
time=”1$time” # time doesn’t always pad its output
(( $time < $critical_time ))
Ker bo zdaj postopek testiranja samodejen, bo git sam dokončal celotno bisekcijo. Brez tega bi jaz moral po vsakem koraku ročno ugotavljati stanje stanja.
$ git bisect run ./lock-timer
running ./lock-timer
Bisecting: 5 revisions left to test after this
[2c6b3949dfe68c7ef8c7cfaaa6d28a7668f90fce] common: removed two “new” wrong locks (cherry picked from commit ff1944760ad070d4186f5f7d87d2fbe5303b567b)
running ./lock-timer
Bisecting: 2 revisions left to test after this
[a664cba6560bf60e17c8ab98e4186913d9aadeb9] moved over from the previous repo: changelog printing customization (cherry picked from commit 387ac9304221e65b443f5ce5d75de2aca49bdec4) (cherry picked from commit bc8ecce21bd90ddfcb06d39a1b445fda28a0bd8a)
running ./lock-timer
Bisecting: 1 revisions left to test after this
[1811339c403d32d65f2927071af43d44665f1d31] common: remove EOL whitespace (cherry picked from commit 1ba309e90a88b28a26d1432fee7cc5712d34b80c)
running ./lock-timer
Bisecting: 0 revisions left to test after this
[020f45f576e256c81cbba72089334396d4562f31] use locking in the rest of libstate (=everywhere) (cherry picked from commit a14b4e055dbf59ff5af4b2efdc787e24a56b5861)
running ./lock-timer
020f45f576e256c81cbba72089334396d4562f31 is first bad commit
commit 020f45f576e256c81cbba72089334396d4562f31
Author: Jaka Kranjc
Date: Tue Jan 15 11:46:21 2008 +0100
use locking in the rest of libstate (=everywhere)
(cherry picked from commit a14b4e055dbf59ff5af4b2efdc787e24a56b5861)
:040000 040000 529cbcde750b49a5f79d4ef8e8f861ae52c8c94d e345e2f004434acb31c46e539dab87dfcd8fa223 M var
bisect run success
Lepo se vidi (škoda da wp popači izpis) kako dela git-bisect in na koncu je očitno, da je problematičen 020f45f576 (”… is first bad commit”), katerega commit (islovar: potrditev?!) message je tudi v celoti izpisan.
Čas za nove optimizacije.
Za konec pa še:
$ git bisect reset
S tem ukazom se git vrne na začetno vejo (bisekcija se dogaja na posebni) in postopek se konča. Uporabno, če gre kaj narobe.
Moram pa obvezno omeniti, da dobljeni rezultat ni nujno pravi krivec. Najdeno stanje je lahko le omogočilo, odkrilo težave drugje. To se mi je tudi že zgodilo.
Čisto korektna bisekcija je nakazala čisto korektno stanje. Vendar je tista sprememba le omogočila drugim hroščem, da so postali “vidni”. Ok, striktno gledano je bila kriva ona in je imel git prav - če bi to spremembo preklical, bi bilo spet vse v redu. Vendar je taista sprememba zaželjena, treba je popraviti tiste druge podlubnike. Tečno.
Če vam zadnji odstavek ni bil jasen, si oglejte naslednji ilustrativni primer evolucije programa:
stanje 1
#!/bin/env bash
echo juuhu
stanje 2
#!/bin/env bash
read -p “kako ti je ime? ” odgovor
echo juuhu, $odgovor
stanje 3
#!/bin/env bash
read -p “kako ti je ime? ” odgovor
if [[ odgovor == “huda mravljica” ]]; then
echo AAAAAAAA
kill $$
else
echo juuhu, $odgovor
fi
Ups, naredil sem napako v testu, primerjam dva niza namesto spremenljivke in niz. Sledi popravek:
stanje 4
#!/bin/env bash
read -p “kako ti je ime? ” odgovor
if [[ $odgovor == “huda mravljica” ]]; then
echo AAAAAAAA
kill $$ #samomor, simulacija sesutja
else
echo juuhu, $odgovor
fi
Zdaj mi pa huda mravljica sporoči, da se skripta sesuje! Napiše “Terminated” in to je to.
Če bi poskušal napako najti z git bisect, bi prišel do stanja 4 (nikamor), ker je bila prej problematična koda nedostopna (tisti slab test je bil vedno neresničen (false)), čeprav se je napaka pojavila v stanju 3.