Perełki CLI: git bisect

Obrazek użytkownika jsobiecki
Perełki CLI: git bisect

Wprowadzenie

Każdy programista używa takich narzędzi, z którymi pracuje mu się najlepiej, czasami organizacje narzucają je z góry. Niemniej, nagminnie obserwowałem, jak programiści, skupiając się na zaawansowanych i skomplikowanych narzędziach, często pomijali pewne mniej rozpowszechnione funkcje, które w teorii są wszystkim znane i każdy wie, do czego służą. Jedną z nich jest polecenie gita: git bisect.

git bisect - czyli jak szybko znaleźć dziurę w kodzie

Podczas życia projektów informatycznych tworzy się na prawdę dużo commitów. Bardzo, bardzo dużo :) W niektórych etapach, kiedy bardzo dużo kodu jest tworzone od nowa lub zmieniane, stosunkowo łatwo (mimo procesu testowego) wprowadzić błąd..

W przypadku, gdy bug nie jest oczywisty, jednym z podejść jest znalezienie momentu życia projektu, w którym go wprowadzono. Zamiast analizować setki commitów, a co za tym idzie, gigantyczną ilość kodu, wystarczy sprawdzić jeden.

I teraz przechodzimy do podstawowej trudności: Jak w efektywny sposób znaleźć commit, który wprowadził błąd? Naiwne podejście to znalezienie jakiegoś starego, działającego commita i powolne przesuwanie się commit po commicie, aż w koncu natrafimy na usterkę. Tutaj pojawia się pewien problem - jeśli np. wybraliśmy przedział ok. 300 commitów, czas potrzebny na weryfikacje może być naprawdę długi. W grę wchodzi powtórzenie procesu weryfikacji 300 razy, co może być traumatycznym przeżyciem dla osoby testującej. By usprawnić proces, można użyć skryptu w bash i wywołania testu automatycznego (jeśli go mamy). I tutaj pojawia się proste, a jednocześnie niesamowicie efektywne narzędzie, jakim jest git bisect.

Jak to działa?

Idea działania tego narzędzia jest prosta i nawiązuje do matematycznego algorytmu szukania miejsc zerowych funkcji ciągłych (https://pl.wikipedia.org/wiki/Metoda_r%C3%B3wnego_podzia%C5%82u). Innymi słowy, dzielimy przedział na pół, sprawdzamy czy błąd występuje w połowie. Jeśli występuje - powtarzamy algorytm w przedziale commitów wcześniejszych niż bieżący commit. Jeśli nie występuje, szukamy w późniejszych. Algorytm kończy się, gdy nasz przedział będzie jednoelementowy. Warto zwrócić uwagę, że ten algorytm znajduje błąd w log_2(N) krokach. Dla przykładu, jeśli mamy do sprawdzenia 300 commitów, znajdziemy bug w 9 krokach.

Ok, przejdźmy do przykładu. Załóżmy, że nie mamy automatycznego testu, który zweryfikowałby problem i robimy to ręcznie. Przykładowa sesja z git bisect wygląda następująco:

# zaczynamy od wyznaczenia zakresów
$ git bisect start
$ git bisect bad # bieżący commit jest zły
$ git bisect good da8d6c0d81b629 # oznaczamy stary, poprawny commit

Bisecting: 66 revisions left to test after this (roughly 6 steps)
[b7161b5b98b6b2fd46e0e5c0472f3949b9f25a7f] refs: #2630 | Opis commita
# wykonujemy test ręcznie - commit zły
$ git bisect bad
Bisecting: 32 revisions left to test after this (roughly 5 steps)
[dfa7f15eed8236b732844124c83b5711f7167d46] Kolejny commit

# sesja odpowiedzi git bisect good i git bisect bad w zależności od wyników testu...
...

Wynik wyszukiwania:

b7161b5b98b6b2fd46e0e5c0472f3949b9f25a7f is the first bad commit
commit b7161b5b98b6b2fd46e0e5c0472f3949b9f25a7f
Author: Jon Smith
Date:   Thu May 3 10:58:45 2018 +0200

    refs: #2630 | Bad commit description.

Jak widać, w 6 krokach udało się przeskanować przedział 70 commitów.

Automatyzacja

Jeśli posiadamy automatyczne testy, git bisect może je wykorzystać. W tym celu, po oznaczeniu pierwotnego przedziału, wystarczy użyć opcji run: git bisect run ./phpunit.sh.

Przygotowanie takiego skryptu zostawiam jako zadanie domowe :)