Git Bisect ile Hata Yaratan Commit Tespiti
Üretim ortamında bir şeyler patladığında ve “bu dün çalışıyordu, şimdi çalışmıyor” dediğinizde, aklınıza ilk gelen şey muhtemelen son commit’i geri almak olur. Ama ya son 50 commit’ten biri sorun yaratıyorsa? Ya release branch’ine yapılan onlarca merge arasında bir yerde gizlenmiş bir regresyon varsa? İşte tam bu noktada git bisect devreye girer ve sizi saatlik manuel incelemeden kurtarır.
Ben yıllarca bu tür durumlarda commit geçmişini tek tek inceleyip zaman kaybettim. git bisect‘i öğrendikten sonra “neden bunu daha önce öğrenmedim” dedim açıkçası. Binary search algoritmasını commit geçmişine uygulayan bu araç, n adet commit arasında log2(n) adımda hatalı commit’i bulur. 1000 commit’lik bir geçmişte en fazla 10 adımda sorunu bulursunuz.
Git Bisect Nedir ve Nasıl Çalışır
git bisect özünde ikili arama (binary search) yapar. Siz ona “bu commit iyi çalışıyor, bu commit kötü” dersiniz; o da ikinisinin ortasındaki commit’i checkout eder. Siz “bu da kötü” dersiniz, bir önceki yarıya geçer. “Bu iyi” dersiniz, diğer yarıya. Bu şekilde her adımda arama alanını yarıya indirir ve sonunda suçlu commit’i teşhis eder.
Temel döngü şu şekilde işler:
git bisect start
git bisect bad HEAD # Şu anki durum bozuk
git bisect good v2.3.1 # Bu tag'de her şey iyiydi
# Git ortadaki commit'e geçer, siz test edersiniz
git bisect good # Veya: git bisect bad
# Tekrar test, tekrar işaretleme...
git bisect reset # Bitince eski halinize dönün
Bu kadar basit. Ama detaylara girince iş çok daha ilginçleşiyor.
Gerçek Bir Senaryo: API Latency Patlaması
Geçen ay yaşadığım bir olayı anlatayım. Mikro servis mimarimizde bir servisin response time’ı aniden 50ms’den 800ms’ye çıktı. Monitoring alertleri patladı, müşteriler şikayetçi. Son 3 günde 47 commit gelmiş, 4 farklı developer çalışmış.
# Önce mevcut durumu doğrulayalım
git log --oneline -20
# a3f8c21 Fix user authentication edge case
# b2e9d14 Update dependencies
# c1d7a03 Refactor database connection pool
# d0e6b92 Add new endpoint for reports
# ...
# Bisect başlatıyoruz
git bisect start
git bisect bad HEAD
# 3 gün öncesini iyi biliyoruz
git bisect good a1b2c3d4
Git bana şunu söyledi: “Bisecting: 23 revisions left to test after this (roughly 5 steps)”. 47 commit, 5 adım. Harika.
Her adımda servisimi ayağa kaldırdım ve basit bir curl ile latency ölçtüm:
# Her checkout'tan sonra çalıştırdığım test scripti
curl -w "@curl-format.txt" -o /dev/null -s "http://localhost:8080/api/users"
Curl format dosyam şöyle:
# curl-format.txt içeriği
time_namelookup: %{time_namelookup}n
time_connect: %{time_connect}n
time_appconnect: %{time_appconnect}n
time_pretransfer: %{time_pretransfer}n
time_redirect: %{time_redirect}n
time_starttransfer: %{time_starttransfer}n
time_total: %{time_total}n
5 adım sonra Git bana “c1d7a03 Refactor database connection pool” commit’ini işaret etti. Koda baktım, connection pool size’ı yanlışlıkla 100’den 1’e düşürmüşler. Tespit süresi: 12 dakika.
Otomatik Bisect: Asıl Güç Buradan Geliyor
Manuel bisect iyi ama her adımda servisi kaldırıp test etmek yorucu. git bisect run komutu ile testi tamamen otomatize edebilirsiniz. Bu, uzun CI pipeline’larında veya karmaşık test senaryolarında hayat kurtarır.
Test script’iniz 0 döndürürse “iyi”, 0’dan farklı döndürürse “kötü” olarak işaretlenir. 125 özel bir değer, o commit’i “test edilemez” olarak skip eder (mesela derleme hatası varsa).
#!/bin/bash
# test_latency.sh
# Servisi build et
make build 2>/dev/null
if [ $? -ne 0 ]; then
exit 125 # Skip - derleme hatası
fi
# Servisi arka planda başlat
./myservice &
SERVICE_PID=$!
sleep 2
# Testi çalıştır
LATENCY=$(curl -w "%{time_total}" -o /dev/null -s "http://localhost:8080/api/users")
# Servisi durdur
kill $SERVICE_PID 2>/dev/null
# 200ms threshold
python3 -c "exit(0 if float('$LATENCY') < 0.200 else 1)"
# Script'i çalıştırılabilir yap ve bisect'e ver
chmod +x test_latency.sh
git bisect run ./test_latency.sh
Git tamamen otomatik olarak commit’leri gezip testi çalıştıracak ve suçlu commit’i bulacak. Siz kahvenizi içebilirsiniz.
Unit Test ile Otomatik Bisect
Bir regresyon bulduğunuzda o regresyonu test eden bir unit test yazın, sonra bisect’i o test üzerinden çalıştırın. Bu yaklaşım hem hatayı bulur hem de gelecekte aynı hatanın tekrarlanmasını engeller.
# Önce failing testi yazıp commit etmeyin, sadece dosyaya kaydedin
# Sonra bisect'i bu testle çalıştırın
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# Her checkout'ta sadece ilgili testi çalıştır
git bisect run python -m pytest tests/test_user_service.py::test_response_time -x
Python dışında Go, Java, Node.js projelerinde de aynı mantık işler:
# Go projesi için
git bisect run go test ./... -run TestUserServiceLatency
# Node.js için
git bisect run npm test -- --grep "UserService latency"
# Maven projesi için
git bisect run mvn test -Dtest=UserServiceTest#testLatency -q
Karmaşık Durum: Derleme Hatası Veren Commit’leri Atlamak
Gerçek projelerde bazen aradaki commit’ler derlenmez, test bağımlılıkları eksiktir veya başka nedenlerle test edilemez durumdadır. Bu durumda git bisect skip kullanırsınız.
git bisect start
git bisect bad HEAD
git bisect good v1.5.0
# Git bir commit önerir, siz bakarsınız ki derleme hatası var
make build
# Error: undefined reference to 'new_function'
# Bu commit'i atla
git bisect skip
# Birden fazla commit'i de atlayabilirsiniz
git bisect skip a1b2c3 d4e5f6 g7h8i9
Otomatik modda 125 exit code’u bu işi yapıyor demiştik. Ama bazen commit range’ini baştan belirlemek daha temiz olur:
# Belirli commit'leri baştan hariç tut
git bisect start HEAD v1.5.0 --
git bisect skip $(git log --pretty=format:"%H" v1.5.0..v1.6.0 -- broken_module/)
Git Bisect Log ve Replay: Oturumu Kaydetmek
Uzun bisect oturumlarında ya da takım arkadaşınızla paylaşmanız gereken durumlarda log ve replay özellikleri çok işe yarar.
# Mevcut bisect oturumunu kaydet
git bisect log > bisect_session.log
# Dosya içeriği şöyle görünür:
# git bisect start
# # bad: [a3f8c21...] Fix user authentication edge case
# git bisect bad a3f8c21
# # good: [a1b2c3d4...] Release v1.5.0
# git bisect good a1b2c3d4
# # good: [c9d8e7f...] Update README
# git bisect good c9d8e7f
# ...
# Başka bir makinede veya başka bir zamanda devam etmek için
git bisect reset
git bisect replay bisect_session.log
Bu özelliği özellikle şu senaryoda kullandım: Production’da bir bug vardı, bisect yaparken mesai bitti. Sabah geldiğimde replay ile kaldığım yerden devam ettim. Çok temiz bir çözüm.
Görselleştirme: Neyin Nerede Olduğunu Anlamak
Bisect yaparken hangi commit’lerin “good/bad/untested” olduğunu görmek bazen kafa karıştırıcı olabilir. Şu komutlar durumu netleştirir:
# Bisect sürecini görsel olarak takip et
git bisect visualize
# Ya da kısa formu:
git bisect view
# Bu gitk arayüzünü açar, renk kodlarıyla gösterir
# Terminalden çalışıyorsanız:
git bisect visualize --oneline
# Commit geçmişini bisect durumlarıyla birlikte görmek için
git log --oneline --decorate BISECT_HEAD
git log --oneline $(git bisect good)..$(git bisect bad)
Büyük ekiplerde bu görselleştirme, bisect oturumunu başkasına devretmek zorunda kaldığınızda durumu aktarmak için çok kullanışlı.
İleri Seviye: Birden Fazla Dosya veya Dizin Üzerinde Bisect
Bazen biliyorsunuzdur ki sorun sadece belirli bir modülden kaynaklanıyor. Bisect’i o dizinle sınırlandırabilirsiniz:
# Sadece belirli dizindeki değişiklikleri dikkate al
git bisect start HEAD v1.0.0 -- src/database/
# Ya da birden fazla path
git bisect start HEAD v1.0.0 -- src/api/ src/middleware/
Bu yaklaşım özellikle monorepo yapılarında çok işe yarar. Onlarca servisin bulunduğu bir repoda sadece services/payment/ altındaki değişikliklere odaklanabilirsiniz.
Daha gelişmiş bir kullanım senaryosu: bir commit’in birden fazla bileşene dokunup dokunmadığını kontrol etmek.
#!/bin/bash
# smart_test.sh - Hangi dosyalar değişmiş, ona göre test çalıştır
CHANGED_FILES=$(git diff HEAD~1 HEAD --name-only)
if echo "$CHANGED_FILES" | grep -q "^src/database/"; then
make test-database
exit $?
fi
if echo "$CHANGED_FILES" | grep -q "^src/api/"; then
make test-api
exit $?
fi
# İlgili dosya yoksa skip
exit 125
Bisect Sonucunu Analiz Etmek
Bisect hatalı commit’i bulduğunda işiniz bitmemiş demektir. Asıl analiz orada başlar.
# Bisect tamamlandığında Git şöyle der:
# b3c4d5e6 is the first bad commit
# commit b3c4d5e6
# Author: Ahmet Yilmaz <[email protected]>
# Date: Mon Oct 14 14:32:11 2024 +0300
#
# Refactor database connection pool
# Bu commit'te tam olarak ne değişmiş?
git show b3c4d5e6
# Sadece değişen dosyalar
git show b3c4d5e6 --stat
# Belirli bir dosyadaki değişiklik
git show b3c4d5e6 -- src/database/pool.go
# Hatalı commit'i bulduktan sonra temizlik
git bisect reset
# Bu sizi bisect başlamadan önceki HEAD'e döndürür
# Şimdi ne yapacaksınız? Fix için branch açın
git checkout -b fix/database-pool-size
git revert b3c4d5e6 # Ya da manuel fix yapın
Burada bir not: git revert her zaman iyi bir fikir olmayabilir. Bazen o commit’teki değişikliğin bir kısmı doğruydu, sadece bir satır hatalıydı. O durumda ya cherry-pick yapıp düzeltirsiniz ya da direkt fix commit’i atarsınız.
Bisect ile Performans Regresyonlarını Bulmak
Fonksiyonel bug’lar dışında performans regresyonları için de bisect mükemmel çalışır. Burada threshold belirleme kritik.
#!/bin/bash
# perf_test.sh
# Build
make build -s
if [ $? -ne 0 ]; then exit 125; fi
# 10 kez çalıştır, ortalamasını al
TOTAL=0
for i in {1..10}; do
T=$(./benchmark_tool --metric=p99_latency --quiet)
TOTAL=$(python3 -c "print($TOTAL + $T)")
done
AVG=$(python3 -c "print($TOTAL / 10)")
echo "Average P99 latency: ${AVG}ms"
# 100ms threshold
python3 -c "import sys; sys.exit(0 if $AVG < 100 else 1)"
git bisect start
git bisect bad HEAD
git bisect good v2.0.0
git bisect run ./perf_test.sh
Bu yaklaşımla bir projede memory leak’in tam olarak hangi commit’te başladığını bulmuştum. 3 haftayı kapsayan 180 commit arasında 8 adımda buldu. Elle incelemiş olsaydım saatlerce uğraşırdım.
Sık Yapılan Hatalar ve Dikkat Edilecekler
Birkaç tuzaktan bahsetmeden geçemeyeceğim.
State’e bağımlı testler: Eğer testiniz bir önceki çalıştırmadan kalan state’e (veritabanı kaydı, dosya, cache) bağımlıysa bisect yanlış sonuç verebilir. Her test çalıştırması öncesinde temiz bir başlangıç durumu sağlayın.
Flaky testler: Bazen geçip bazen kalmayan testleri bisect’le kullanmayın. Bisect’in sonuçları tutarlı testlere dayanması lazım, yoksa sizi yanlış commit’e yönlendirir.
Merge commit’ler: Merge commit’ler bazen kafa karıştırır. Git bisect bunları da işler ama merge’den gelen değişiklikler bazen karmaşık olabilir. --first-parent flag’i ile sadece ana branch’teki commit’lere odaklanabilirsiniz:
git log --first-parent --oneline -20 # Durumu görmek için
git bisect start --first-parent # Sadece ana branch commit'lerine bak
git bisect bad HEAD
git bisect good v1.0.0
Çevre farklılıkları: Bisect yaparken her commit’te çevre aynı olmalı. Özellikle bağımlılık değişikliklerinde npm install veya go mod download gibi komutları test script’inizin başına ekleyin.
Takım İçin Bisect Kültürü Oluşturmak
Bisect bireysel bir araç olmaktan çıkıp takım pratiği haline geldiğinde asıl değerini gösteriyor. Birkaç öneri:
Postmortem dökümanlarınıza “nasıl bulundu” bölümü ekleyin ve bisect oturumlarını git bisect log çıktısıyla kaydedin. Aynı tür regresyon tekrar yaşandığında geçmiş oturum size ipucu verir.
Her servis veya uygulama için standart bir bisect_test.sh scripti hazırlayın ve repo’nun kök dizinine koyun. Yeni başlayan bir mühendis bile bisect yapabilsin diye. Bu script şunları içermeli: bağımlılık kurulum, build, servis başlatma, otomatik test, temizlik.
CI/CD pipeline’ınıza “regression finder” aşaması ekleyebilirsiniz. Otomatik testler başarısız olduğunda bisect’i tetikleyen ve suçlu commit’i direkt PR’a yorum olarak bırakan bir sistem kurmak mümkün. GitHub Actions veya GitLab CI ile bu tamamen otomatize edilebilir.
Sonuç
git bisect araç kutunuzdaki en az kullanılan ama en güçlü araçlardan biri. Çoğu sysadmin ve developer bunu ya hiç bilmiyor ya da sadece adını duymuş. Oysa binary search algoritmasının gücü sayesinde yüzlerce commit arasında hatalı olanı logaritmik sürede buluyor.
Özellikle vurgulayacağım nokta şu: git bisect run ile otomatik bisect yapmak, manuel bisect’ten çok daha değerli. İyi bir test scripti yazarsanız, kahvenizi alıp geldiğinizde suçlu commit ekranda sizi bekliyor.
Bir sonraki production olayında ilk 20 commit’i tek tek incelemek yerine git bisect start yazın. Zaman farkı ilk kullanımda inanılır gibi gelmiyor, ama ilerleyen commit geçmişlerinde bu fark saatlerle ölçülüyor.
Son bir tavsiye: bisect oturumu açtığınızda git bisect log‘u düzenli kaydedin. Hem kendiniz için not olur, hem de olayı takımınıza anlatırken commit bazlı bir hikaye anlatabilirsiniz. “47 commit içinde 5 adımda bulduk” demek, “saatler uğraştık bulduk” demekten çok daha tatmin edici.
