CI Anda merah di main, tapi kemarin masih hijau. Di suatu tempat di antaranya, bug menyusup masuk. Anda bisa menggulir 200 commit, membaca diff dan menebak. Anda bisa bertanya di Slack dan berharap seseorang ingat menyentuh kode yang relevan. Atau Anda bisa membiarkan Git mengerjakannya untuk Anda.
git bisect adalah tool pencarian biner untuk riwayat commit Anda. Anda menandai satu commit sebagai buruk dan satu sebagai baik. Git checkout titik tengah. Anda mengujinya, menandainya baik atau buruk, dan Git mengulangi. Dalam 8 langkah, ia bisa mengisolasi satu commit dari 256. Dalam 12 langkah, ia bisa menemukan jarum di tumpukan 4.096. Ini adalah hal terdekat yang dimiliki Git dengan debugger untuk riwayat itu sendiri.
Mengapa bisection manual membuang-buang waktu Anda
Pengembang sudah melakukan bisect secara manual tanpa menyadarinya. Anda checkout commit yang lebih lama, menjalankan test, dan berpikir “masih rusak, harus lebih ke belakang.” Kemudian Anda checkout yang lebih lama lagi, dan test lolos. Sekarang Anda tahu bug ada di suatu tempat di antara kedua titik itu. Jadi Anda memilih yang di tengah dan terus menyempitkan.
Itu adalah binary search. git bisect mengotomatiskan bookkeeping sehingga Anda tidak kehilangan jejak commit mana yang sudah Anda uji. Lebih penting lagi, ini mencegah heuristik malas “kemungkinan besar di refactor besar dari hari Selasa” yang mengirim Anda ke lubang kelinci yang salah selama dua jam.
Nilai sebenarnya bukan kecepatan, meskipun memang lebih cepat. Nilai sebenarnya adalah kebenaran. Ketika Anda frustrasi dan berburu bug jam 6 sore, Anda akan membuat kesalahan. Anda akan lupa rebuild setelah checkout. Anda akan menguji commit yang salah dua kali. Anda akan salah membaca hasil test dan menandai commit buruk sebagai baik, menghancurkan ruang pencarian Anda. git bisect menegakkan disiplin yang tidak Anda miliki saat kesal.
Bagaimana pencarian biner sebenarnya bekerja
Git tidak mencari secara kronologis. Ia mencari secara topologis, berjalan melalui commit graph untuk menemukan titik tengah antara commit yang diketahui baik dan diketahui buruk Anda. Ini penting ketika riwayat Anda memiliki merge, karena titik tengah kronologis mungkin bahkan tidak bisa dijangkau dari kedua endpoint.
Berikut yang terjadi di balik layar. Anda memulai bisect dan memberikan batas:
git bisect start
git bisect bad HEAD # current commit is broken
git bisect good v2.1.0 # this release was fine
Git menghitung jumlah commit di antara kedua titik tersebut. Ia checkout yang persis di tengah dan menunggu Anda mengujinya. Anda menjalankan reproduksi Anda, melihat apakah bug ada, dan memberi tahu Git:
git bisect bad # this commit has the bug
git bisect good # this commit is clean
Git membuang setengah ruang pencarian dan mengulangi. Ketika hanya tersisa satu commit, ia berhenti dan menunjukkan commit buruk pertama. Outputnya mencakup hash commit, penulis, tanggal, dan pesan. Tidak ada ambiguitas. Commit inilah yang memperkenalkan bug, titik.
Langkah konkret dengan perintah nyata
Misalkan integration test Anda mulai gagal pagi ini. Anda tahu mereka lolos di release tagged terakhir, v1.4.0. Berikut sesi lengkapnya:
# Start the session
git bisect start
# Mark the current HEAD as bad
git bisect bad HEAD
# Mark the last known good release
git bisect good v1.4.0
# Git checks out a midpoint commit automatically
# You run your reproduction script or test suite:
npm test -- --grep "checkout flow"
# Tests fail. Mark it bad.
git bisect bad
# Git checks out another midpoint. Run tests again.
npm test -- --grep "checkout flow"
# Tests pass. Mark it good.
git bisect good
# Repeat until Git tells you:
# "<commit-hash> is the first bad commit"
Di akhir, Git meninggalkan Anda di commit buruk. Anda bisa memeriksanya dengan git show, membuat perbaikan, lalu membersihkan:
git bisect reset
Ini mengembalikan Anda ke branch tempat Anda berada sebelum memulai. Jika Anda lupa reset, Anda akan tetap berada di detached HEAD dan bertanya-tanya mengapa commit berikutnya tidak ada di branch Anda. Saya sudah melakukan ini. Itu memalukan.
Mengotomatiskan seluruh proses dengan skrip
Versi manual masih mengharuskan Anda menjalankan test dan mengetik good atau bad berulang kali. Jika reproduksi Anda adalah satu perintah yang keluar 0 untuk sukses dan non-zero untuk gagal, Anda bisa menyerahkan semuanya ke Git:
git bisect start
git bisect bad HEAD
git bisect good v1.4.0
# Hand over control to an automated script
git bisect run npm test -- --grep "checkout flow"
Git akan checkout commit, menjalankan perintah Anda, dan mengklasifikasikan hasil secara otomatis. Ketika selesai, Anda mendapatkan output “first bad commit” yang sama tanpa menyentuh keyboard. Di sinilah git bisect beralih dari berguna menjadi tak tergantikan.
Skrip Anda tidak perlu menjadi test suite. Ia bisa menjadi apa pun yang bisa dieksekusi yang mengembalikan exit code yang bermakna. Berikut skrip shell minimal yang memeriksa pesan log tertentu:
#!/bin/bash
# reproduce-bug.sh
# Exit 0 if the bug is NOT present (good)
# Exit 1 if the bug IS present (bad)
if curl -s http://localhost:3000/api/health | grep -q "database_timeout"; then
exit 1 # bug is present
fi
exit 0 # bug is not present
Jalankan:
chmod +x reproduce-bug.sh
git bisect run ./reproduce-bug.sh
Kontrak exit code ketat. Exit 0 berarti “baik,” exit 1 hingga 124 berarti “buruk,” dan exit 125 berarti “lewati commit ini, tidak bisa diuji.” Exit 125 berguna ketika commit tidak berhasil dikompilasi, atau server tidak bisa memulai karena perubahan konfigurasi yang tidak terkait. Git akan melewati commit itu dan mencari di sekitarnya.
Di mana bisect terpecah: hal-hal yang perlu diwaspadai
git bisect mengasumsikan bug Anda monoton. Setelah commit memperkenalkannya, setiap commit turunan juga buruk. Jika bug berkedip-kedip, muncul dan menghilang di seluruh commit, pencarian biner hancur. Anda akan mendapatkan hasil tidak masuk akal atau Git akan mengeluh bahwa ia tidak bisa mengisolasi satu commit.
Bug non-deterministik adalah pelanggar terburuk. Race condition yang gagal 10% dari waktu akan sesekali menandai commit buruk sebagai baik, merusak pencarian. Jika reproduksi Anda flaky, perbaiki keflakukan terlebih dahulu. Atau jalankan test berkali-kali di skrip Anda dan hanya tandai commit baik jika lolos setiap kali. Ini lebih lambat tapi lebih andal.
Artifact build adalah jebakan lain. Jika Anda beralih dari commit yang mengubah versi tool build ke yang tidak, node_modules atau biner yang dikompilasi Anda mungkin tidak cocok dengan kode yang di-checkout. Selalu bersihkan dan rebuild di dalam skrip reproduksi Anda jika proyek Anda memiliki langkah build.
#!/bin/bash
# safer-reproduce.sh
rm -rf node_modules dist
npm ci
npm run build
npm test -- --grep "checkout flow"
Ini menambah beberapa detik per iterasi, tapi menghilangkan seluruh kategori false positive di mana bug sebenarnya ada di artifact yang sudah usang.
Apa yang tidak dilakukan bisect
git bisect menemukan commit. Ia tidak memberi tahu Anda mengapa commit itu buruk. Sebuah refactor 5.000 baris yang menyentuh empat puluh file bisa menjadi commit buruk pertama, dan Anda masih harus membaca diff untuk memahami baris mana yang bersalah. Bisect menyempitkan ruang lingkup Anda dari “di suatu tempat sebulan terakhir” menjadi “di suatu tempat di diff ini.” Sisanya masih tugas Anda.
Ini juga tidak membantu dengan bug yang ada di codebase tapi tidak pernah tertangkap test. Jika test suite Anda sudah hijau ketika bug diperkenalkan, bisect tidak akan menyelamatkan Anda. Anda memerlukan reproduksi terlebih dahulu. Bisect adalah tool pencarian, bukan tool deteksi.
Kapan menggunakan bisect alih-alih blame atau log
git blame bagus ketika Anda sudah tahu file mana yang berisi bug. git log --grep bagus ketika Anda mengingat sesuatu tentang pesan commit. Bisect untuk ketika Anda tidak tahu keduanya. Anda hanya tahu bahwa itu berfungsi di titik A dan gagal di titik B, dan ruang di antaranya terlalu besar untuk dianalisis.
Jika tim Anda menjalankan CI di setiap commit, Anda kadang bisa melakukan bisect lebih cepat dengan membaca riwayat CI. Tapi CI hanya menangkap apa yang diujinya. Regression performa, bug visual, atau error logika halus yang dilewatkan test Anda tidak akan muncul di log CI. Bisect bekerja untuk reproducer apa pun yang bisa Anda skrip, terlepas dari apa yang divalidasi CI Anda.
Cara menjadikan bisect bagian dari workflow Anda
Anda tidak memerlukan setup khusus. Perintahnya sudah built-in ke Git. Tapi ada dua kebiasaan yang membuat bisect lebih mulus ketika Anda benar-benar membutuhkannya.
Pertama, tag release Anda. Bisect memerlukan commit yang diketahui baik, dan tag adalah cara termudah untuk menyediakannya. Jika release terakhir Anda tiga minggu lalu dan Anda tahu ia bersih, git bisect good v1.4.0 adalah titik jangkar satu baris.
Kedua, pertahankan reproduksi Anda minimal. Reproduksi yang membutuhkan tiga puluh detik untuk dijalankan masih bisa ditoleransi untuk delapan iterasi. Reproduksi yang membutuhkan sepuluh menit adalah penyiksaan. Sebelum Anda melakukan bisect, habiskan lima menit menyempitkan reproduksi ke perintah sekecil mungkin. Diri Anda di masa depan akan berterima kasih.
Ketika selesai, jalankan git bisect reset segera. Sebuah detached HEAD dengan perubahan yang belum di-commit adalah cara bagus untuk kehilangan pekerjaan. Saya mempelajarinya dengan cara yang sulit agar Anda tidak perlu.