Testy są zielone od trzech tygodni. Dziś npm test pada na asercji, która nie ma nic wspólnego z tym, co właśnie zmieniłeś, a git log v1.4.0..HEAD pokazuje 340 commitów od czterech osób od ostatniego release’a, o którym ktoś był pewien, że działa. Przeczytanie każdego diffa kosztuje cały dzień. Losowe przełączanie się między commitami idzie szybciej, ale wciąż nie jest planem.

git bisect zamienia to w wyszukiwanie binarne. Podajesz mu jeden commit, o którym wiesz, że jest dobry, i jeden, o którym wiesz, że jest zepsuty; narzędzie robi checkout commita w połowie drogi między nimi i czeka, aż przetestujesz właśnie ten commit i zgłosisz wynik. Każda odpowiedź zmniejsza zakres o połowę. 340 commitów da się rozstrzygnąć w maksymalnie 9 testach, bo 2^9 = 512.

Jak git bisect zmniejsza zakres o połowę przy każdym kroku

Wyobraź sobie 340 commitów ustawionych w linii, od najstarszego do najnowszego, ze znanym dobrym tagiem na jednym końcu i zepsutym HEAD na drugim. git bisect nie przechodzi przez tę linię. Skacze na środek, czeka na werdykt, a potem odrzuca tę połowę, którą ten werdykt wyklucza:

1
2
3
4
good                                          bad
 |------------------------|------------------|
                    ^
              przetestuj ten

Powiedzmy, że środkowy commit przechodzi test. Bug jest w nowszej połowie, więc starsza połowa znika na dobre i w tej sesji już nigdy nie zobaczysz tych commitów. Powtarzaj: środek tego, co zostało, testuj, odrzucaj połowę. Po mniej więcej log2(n) rundach zakres kurczy się do jednego commita — tego, w którym zachowanie zmieniło się z działającego na zepsute. To nie zawsze commit, który „spowodował” buga w głębszym sensie. To commit, w którym zmieniło się zachowanie, a to prawie zawsze jest właśnie to, czego potrzebujesz.

Jak zacząć sesję git bisect

Uruchom to z czystego katalogu roboczego. Bisect sam robi checkouty kolejnych commitów w trakcie działania i odmawia startu, jeśli w drzewie roboczym są niezacommitowane zmiany.

1
2
3
git bisect start
git bisect bad HEAD
git bisect good v1.4.0

Git robi checkout punktu środkowego i mówi, ile zostało:

1
2
Bisecting: 169 revisions left to test after this (roughly 8 steps)
[a1b2c3d4e5f6...] Refactor payment retry logic

Uruchom swój test na tym commicie, a potem zgłoś, co znalazłeś:

1
2
npm test
git bisect bad     # test padł tutaj

albo

1
git bisect good    # test przeszedł tutaj

Git automatycznie robi checkout nowego punktu środkowego po każdej odpowiedzi. Testuj i zgłaszaj dalej, aż nie będzie już nic do zawężenia:

1
2
3
4
5
6
a1b2c3d4e5f6789... is the first bad commit
commit a1b2c3d4e5f6789...
Author: Priya Nair <[email protected]>
Date:   Tue Sep 2 11:14:02 2026 +0000

    Refactor payment retry logic

To twoja odpowiedź. Przeczytaj ten diff, nie osiem commitów wokół niego. git bisect reset przywraca cię na branch i commit, od którego zacząłeś, a checkouty zrobione przez bisect po drodze nie zostawiają śladu.

1
git bisect reset

Automatyzacja pętli za pomocą git bisect run

Odpowiadanie good albo bad ręcznie sprawdza się przy błędzie, który widać w ciągu kilku sekund. Przy czymś, co wymaga pełnego przebiegu testów, oddaj całą pętlę skryptowi:

1
2
3
4
git bisect start
git bisect bad HEAD
git bisect good v1.4.0
git bisect run ./bisect-test.sh

bisect-test.sh musi tylko zakończyć się właściwym kodem: 0 oznacza dobry, dowolny kod od 1 do 127 oprócz 125 oznacza zepsuty, a 125 oznacza „nie da się przetestować tego commita, pomiń go”.

1
2
#!/bin/sh
npm test

Większość test runnerów już kończy się kodem 0 przy sukcesie i niezerowym przy porażce, więc skrypt często sprowadza się do jednego wywołania runnera. git bisect run robi checkout każdego punktu środkowego, uruchamia skrypt, odczytuje kod wyjścia i sam zgłasza wynik, drukując na końcu to samo podsumowanie „first bad commit”.

Skrypt może robić więcej niż tylko wywoływać test runner. Jeśli regresja widoczna jest tylko w zbudowanym artefakcie, skrypt uruchamiający docker build, a potem sprawdzający obraz, to normalna rzecz do przekazania git bisect run — kroki budowania z Budować i Publikować Obraz Docker z GitHub Actions działają w skrypcie równie dobrze jak w workflow. Weź to pod uwagę: każda runda kosztuje teraz pełny build obrazu.

Jedno ograniczenie dotyczy samego skryptu. Trzymaj go poza zakresem, który bisectujesz, albo upewnij się, że nie zmienił się w tym zakresie. Jeśli skrypt żyje w przeszukiwanej historii, stary commit robi checkout starej wersji skryptu razem ze starym kodem i przestajesz uruchamiać ten sam test na każdym kroku.

Pomijanie commitów, których nie da się przetestować, za pomocą git bisect skip

Niektórych commitów nie da się przetestować osobno: commit w trakcie refaktoringu, który się nie kompiluje, migracja schematu wymagająca stanu bazy danych, którego nie masz. Wymuszenie werdyktu good/bad na takim commicie zatruwa wynik. Zamiast tego wyklucz go:

1
git bisect skip

Bisect robi checkout pobliskiego commita i kontynuuje, omijając lukę. W bisect run kod wyjścia 125 robi to samo automatycznie, więc skrypt umiejący rozpoznać „ten commit nawet się nie kompiluje” może go pominąć zamiast zgłaszać fałszywy bad. Pomiń zbyt wiele sąsiednich commitów i bisectowi zabraknie testowalnego terenu: ostateczna odpowiedź zamienia się w „bug pochodzi z jednego z tych N commitów” zamiast jednego konkretnego.

Kiedy git bisect nie jest właściwym narzędziem

Bisect zakłada, że twój test jest deterministyczny: ten sam commit, ten sam wynik, za każdym razem. Niestabilny test po cichu łamie to założenie. Bisect nie potrafi odróżnić „ten commit jest zepsuty” od „ten test się posypał”, a jedna błędna odpowiedź na początku sprawia, że każda kolejna runda trafia w złą połowę. Napraw albo wyklucz niestabilną asercję przed bisectowaniem, nie po.

Zakłada też, że jeden commit wprowadził jedną, czystą zmianę zachowania. Bug, który wkradł się przez kilka commitów, albo taki, który zależy od zgromadzonego stanu, a nie pojedynczego diffa, nie zbiegnie się do niczego użytecznego. Mocno zrebase’owana historia to łagodniejsza wersja tego samego problemu (zobacz Git Rebase vs Merge: różnice i kiedy który wybrać): pierwszy zepsuty commit znaleziony przez bisect może być zgniecionym commitem łączącym pięć logicznych zmian. Poprawny, ale zbyt gruboziarnisty, żeby przejrzeć go osobno.

Żaden z tych przypadków nie wymaga innego narzędzia, tylko innego punktu startowego. Najpierw spraw, żeby test był wiarygodny, potem wybierz commit good na tyle odległy, żeby mieć pewność, że buga jeszcze tam nie było.

Następnym razem, gdy pojawi się regresja bez oczywistej przyczyny, zaznacz oba końce i odpowiedz good albo bad kilka razy — albo napisz trzylinijkowy skrypt i pozwól, żeby git bisect run odpowiadał za ciebie.

Powiązane artykuły