-
Notifications
You must be signed in to change notification settings - Fork 30
/
git-bisect.tex
116 lines (68 loc) · 5.66 KB
/
git-bisect.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
\tocChapter{$Bisect$}
\emph{Bisect} je git naredba koja se koristi kad tražimo izmjenu u kodu koja je uzrokovala grešku ($bug$) u programu.
Da bi mogli koristiti $bisect$ važno je da:
\begin{itemize}
\item Imamo način kako utvrditi da li se bug manifestira u kodu kojeg trenutno imamo. Na primjer, $unit$ test ili shell skriptu koja provjerava postojanje buga.
\item Znamo da je $bug$ nastao u nekom trenutku u povijesti projekta (tj. sigurni smo da se nije manifestirao u ranijim verzijama aplikacije).
\end{itemize}
Ukoliko su oba kriterija zadovoljena, moramo znati koji je točno raspon commitova u kojima tražimo $bug$.
Uzmimo na primjer da naš projekt ima povijest:
\input{graphs/bisect_1}
Znamo li da je $c$ commit u kojemu se $bug$ nije manifestirao, a zadnje stanje u našem $branch$u $k$ je pozicija gdje se bug manifestirao, onda znamo i da je bug nastao negdje u $commit$ovima između ta dva.
$Bisect$ nije ništa drugo nego binarno pretraživanje po povijesti projekta.
U prvom koraku moramo gitu dati do znanja koji je $commit$ dobar (tj. u kojemu se $bug$ \textbf{nije manifestirao}), a koji je loš (tj. u kojemu se $bug$ \textbf{manifestira}).
I nakon toga slijedi niz iteracija pri čemu nas git prebacuje na neki $commit$ između zadnjeg dobrog i lošeg.
U svakom koraku se interval suzuje sve dok ne dođemo do mjesta kad je problem nastao.
Uzmimo, na primjer, da se trenutno nalazimo u grani u kojoj imamo bug.
Gitu dajemo do znanja da želimo započeti $bisect$ i da je trenutni $commit$ "\textbf{loš}":
\input{git_output/git_bisect_1}
Prebacujemo se na stanje za koje smo sigurni da se $bug$ nije manifestiralo:
\input{git_output/git_bisect_2}
Dajemo mu do znanja da je to "\textbf{dobar} $commit$"\footnote{Naravno, ako niste sigurni probajte otići još malo dalje u povijest sve dok ne dođete do $commit$a gdje se $bug$ sigurno ne manifestira}:
\input{git_output/git_bisect_3}
Git nas sad prebacuje na neki $commit$ između ta dva.
Nama je irelevantno koji je točno.
Jedino što trebamo je isprobati pojavljuje li se $bug$ u kodu koji je trenutno $checkout$an.
Na primjer, kod mene se $bug$ manifestirao, dakle pišem:
\input{git_output/git_bisect_4}
U sljedećem koraku sam na $commit$u gdje $bug$a nema:
\input{git_output/git_bisect_5}
\dots{}i dalje\dots
\input{git_output/git_bisect_6}
\dots{}sve dok u nekom trenutku ne dođemo do krivca:
\input{git_output/git_bisect_7}
Dakle, krivac je $commit$ \verb+b87db36d71038074a1c478c9f9a329d5c1685a02+.
Da bi točno pogledali što je tada promijenjeno, možemo:
\gitoutputcommand{git diff b87db36d7\textasciitilde{}1 b87db36d7}
Ili:
\gitoutputcommand{gitk b87db36d7}
Ukoliko u bilo kojem trenutku $bisect$ želimo prekinuti i vratiti se na mjesto ($commit$) gdje smo bili kad smo započeli, naredba je:
\gitoutputcommand{git bisect reset}
\tocSection{Automatski $bisect$}
$Bisect$ se radi u koracima, a u svakom koraku morate provjeriti je li $bug$ prisutan ili nije.
Ponekad će ta provjera zahtijevati da restartate web server i provjerite u pregledniku ($browser$u), a u drugim slučajevima ćete jednostavno izvrtiti test.
Ukoliko imate spremne integracijske ili $unit$ testove ili pak neku naredbu koju možete pokrenuti u komandnoj liniji onda se postupak traženja $bug$a može automatizirati naredbom \verb+git bisect run+.
Sintaksa je sljedeća:
\gitoutputcommand{git bisect run <naredba>}
Pretpostavka je da ste prethodno već odredili početni \verb+bad+ i \verb+good+ $commit$.
Važno je da naredba koju pokrećete završava sa statusom \verb+0+ ukoliko je stanje ispravno ili brojem od $1$ do $127$ ukoliko nije.
Vrtite li testove s \verb+make test+, možete koristiti:
\gitoutputcommand{git bisect run make test}
\dots{}a koristite li javu i \verb+maven+ možete:
\gitoutputcommand{git bisect run mvn test}
\dots{}ili se bug manifestira na samo jednom testu:
\gitoutputcommand{git bisect run mvn test -Dtest=MojUnitTest}
Nakon toga će git samostalno izvrtiti sve $bisect$ korake i naći prvi $commit$ u kojemu je skripta završila sa statusom između 1 i 127\footnote{To su standardni statusi s kojima programi operativnom sustavu daju do znanja da su završili s nekom greškom}.
Ukoliko se bug nije manifestirao u postojećim testovima, nego ste test napisali naknadno tada vam \verb+make test+ neće pomoći.
Tada možete sami napisati novu skriptu koja će u svakom koraku $bisect$a dodati taj test i izvrtiti ga\footnote{Možete na primjer napraviti posebnu granu s novim testom i onda u svakom koraju napraviti $cherry-pick$ tog testa prije nego izvrtite testove.}.
\tocSection{Digresija o atomarnim $commit$ovima}
$Bisect$ vam neće otkriti točan uzrok problema, samo će vamo točno reći koje su se izmijene dogodile kad je problem nastao, ali i to je često dovoljno kod traženja pravog uzroka.
Nađe li vam $bisect$ da ste u tom $commit$u promijenili 5 linija koda tada barem jedna od tih linija koda nužno mora biti krivac.
Ako ste u tom $commit$u promijenili 100 linija koda među njima je krivac, ali lakše je bug tražiti u 5 nego u 100 izmijenjenih linija.
Radite li $commit$ove koji imaju tisuće promijenjenih linija, onda je puno teže naći uzrok.
No, to je ionako kriv pristup.
Ne bi nikad smjeli snimati više od jedne izmjene u koraku.
Svaki $commit$ bi uvijek trebao predstavljati jednu jedinu logičku cjelinu.
Na primjer, ako ste izmijenili dokumentaciju, ispravili nevezani bug i preformatirali kod u nekoj trećoj klasi -- to ne bi nikako smio biti \textbf{jedan} $commit$ nego \textbf{tri}.
Drugim riječima, treba raditi \textbf{atomarne $commit$ove}.
Na taj način će vam izmjene u kodu uvijek biti male, a i $bisect$ će nam s puno većom preciznošću moći pomoći kod traženja uzroka problema.