Git Reflog ile Silinen Commitleri Kurtarma
Geçen hafta bir ekip arkadaşım panikle geldi: “Yanlış branch’te çalışmışım, her şeyi sildim, commit’ler gitti.” Sakinleştirdim, terminali açtım ve beş dakika içinde kayıp commit’lerin tamamını geri aldık. Sihir mi? Hayır. git reflog.
Git kullanan herkes en az bir kere “Tanrım, her şeyi mahvettim” hissini yaşamıştır. Hard reset atmak, branch silmek, yanlış rebase yapmak… Bunların hepsi telafi edilebilir hatalar, yeter ki paniklemeyesiniz ve reflog’u bilin.
Reflog Nedir, Neden Bu Kadar Güçlüdür?
Git’in commit geçmişi bir ağaç yapısıdır, biliyorsunuz. Ama reflog farklı bir şey: HEAD pointer’ının ve branch referanslarının zamana göre sıralanmış bir günlüğü. Yani siz bir branch’e checkout yaptığınızda, commit attığınızda, reset çektiğinizde, merge veya rebase yaptığınızda, Git bunların hepsini reflog’a kaydeder.
En kritik fark şu: normal git log sadece mevcut commit geçmişinizi gösterir. Eğer bir commit’i “kaybettiyseniz” (hard reset, branch silme vs.), o commit artık hiçbir branch tarafından işaret edilmez, dolayısıyla git log‘da görünmez. Ama reflog’da hala durur, ta ki Git’in garbage collector’ı devreye girene kadar.
Varsayılan olarak reflog kayıtları 90 gün boyunca saklanır. Bu sizin 90 günlük güvenlik ağınız.
git reflog
Çıktı şuna benzer:
a3f9d21 (HEAD -> main) HEAD@{0}: commit: kullanici formu eklendi
b7c4e10 HEAD@{1}: reset: moving to HEAD~2
e91a2f7 HEAD@{2}: commit: validasyon kurallari guncellendi
4d6b8a3 HEAD@{3}: commit: veritabani baglantisi duzeltildi
c2e1f09 HEAD@{4}: checkout: moving from feature/login to main
Soldan sağa okuyun: commit hash’i, referans adı (HEAD@{0}, HEAD@{1}…), ne zaman değiştiği ve ne yapıldığı.
Temel Kullanım: Hard Reset Sonrası Kurtarma
En yaygın felaket senaryosu: git reset --hard attınız ve son birkaç commit’i kaybettiniz.
Diyelim ki şu durumdasınız:
# Bu komutu çalıştırdınız ve pişman oldunuz
git reset --hard HEAD~3
Üç commit sildik. Şimdi reflog’a bakalım:
git reflog show HEAD
f2a8c31 HEAD@{0}: reset: moving to HEAD~3
9b4e7d2 HEAD@{1}: commit: odeme modulu tamamlandi
3c1f0a9 HEAD@{2}: commit: sepet hesaplama eklendi
7e5d4b8 HEAD@{3}: commit: urun listeleme duzeltildi
a1b2c3d HEAD@{4}: commit: anasayfa tasarimi
HEAD@{0} reset işlemimiz. HEAD@{1}, HEAD@{2} ve HEAD@{3} ise kayıp commit’lerimiz. HEAD@{1}, yani 9b4e7d2 hash’li commit, sıfırlamadan önceki son durumumuz. Oraya dönmek için:
git reset --hard HEAD@{1}
# veya hash ile de yapabilirsiniz
git reset --hard 9b4e7d2
Hepsi bu. Commit’ler geri geldi.
Silinen Branch’i Kurtarma
İkinci klasik senaryo: yanlış branch’i sildiniz.
# Refactor işleminizi yaptığınız branch
git branch -D feature/payment-refactor
Oops. Branch gitti ama commit’ler henüz yok. Reflog her branch için ayrı tutuluyor, artı bir de global HEAD reflog’u var. Şöyle bakalım:
git reflog show refs/heads/feature/payment-refactor
Eğer bu branch hakkında reflog kaydı varsa, son commit hash’ini göreceksiniz. Yoksa global HEAD reflog’una bakın:
git reflog | grep "payment-refactor"
d4e5f6a HEAD@{7}: commit: odeme api entegrasyonu
d4e5f6a HEAD@{8}: checkout: moving from main to feature/payment-refactor
d4e5f6a hash’ini bulduk. Bu commit’i yeni bir branch olarak kurtaralım:
git checkout -b feature/payment-refactor d4e5f6a
# veya
git branch feature/payment-refactor d4e5f6a
Branch’iniz, üzerindeki tüm commit geçmişiyle birlikte geri döndü.
Interactive Rebase Sonrası Kurtarma
Rebase işlemleri reflog’un en kıymetli olduğu anlardır. Özellikle git rebase -i ile commit’leri squash ettiğinizde, sıraladığınızda veya drop ettiğinizde işler ters gidebilir.
Diyelim ki 5 commit’i 1’e squash ettiniz ve sonuç içinizden tam çıkmadı, orijinal commit’lere dönmek istiyorsunuz:
git reflog
a9b8c7d HEAD@{0}: rebase -i (finish): returning to refs/heads/feature/cleanup
a9b8c7d HEAD@{1}: rebase -i (squash): buyuk refactor commit
e3f4g5h HEAD@{2}: rebase -i (start): checkout main
...
b1c2d3e HEAD@{6}: commit: 5. degisiklik
9f8e7d6 HEAD@{7}: commit: 4. degisiklik
8e7d6c5 HEAD@{8}: commit: 3. degisiklik
7d6c5b4 HEAD@{9}: commit: 2. degisiklik
6c5b4a3 HEAD@{10}: commit: 1. degisiklik
Rebase başlamadan önceki duruma dönmek için:
git reset --hard HEAD@{6}
Orijinal 5 commit’iniz geri geldi.
Belirli Bir Commit’i Başka Bir Branch’e Taşıma
Bazen kayıp commit’in tamamına değil, sadece o commit’teki değişikliklere ihtiyaç duyarsınız. İşte bu noktada cherry-pick devreye girer:
# Reflog'dan istediğiniz commit hash'ini bulun
git reflog --all | head -20
# Sadece o commit'in değişikliklerini mevcut branch'e uygulayın
git cherry-pick a3b4c5d
Örneğin production’da kritik bir bug fix commit’i yanlış branch’te yapmışsınız. Reflog’dan hash’i bulun, doğru branch’e geçin, cherry-pick yapın:
git checkout main
git cherry-pick <kayip-commit-hash>
Reflog ile Zaman Bazlı Sorgulama
Reflog sadece sıralı index ile değil, zaman referansıyla da kullanılabilir. Bu özellikle “dün sabah yaptığım commit” gibi durumlarda işinize yarar:
# 1 saat önceki HEAD durumu
git show HEAD@{1.hour.ago}
# Dün sabah 9'daki durum
git show HEAD@{yesterday}
# 2 gün önceki durum
git checkout HEAD@{2.days.ago}
Zaman referanslı log için:
git reflog --date=relative
f2a8c31 HEAD@{3 minutes ago}: commit: son duzeltme
9b4e7d2 HEAD@{2 hours ago}: commit: odeme modulu
3c1f0a9 HEAD@{Yesterday at 16:30}: commit: sepet hesaplama
Daha okunabilir bir format için:
git reflog --format='%C(auto)%h %<(20)%gd %C(blue)%cr%C(reset) %gs'
Stash Reflog’u: Kaybolan Stash’leri Kurtarma
Az bilinir ama çok değerli bir bilgi: stash’ler de reflog’da izlenir. Eğer git stash drop veya git stash clear yaptıysanız:
git reflog show stash
e7f8g9h stash@{0}: WIP on main: kritik fix
d6e7f8g stash@{1}: WIP on feature/login: login sayfasi degisiklikleri
c5d6e7f stash@{2}: WIP on main: test degisiklikleri
Sildiğiniz stash’i kurtarmak için:
git stash apply stash@{2}
# veya direkt hash ile
git stash apply c5d6e7f
Stash reflog’u daha kısa süre tutuluyor olabilir, bu yüzden stash’leri mümkün olduğunca açıklayıcı mesajlarla oluşturun:
git stash save "kullanici auth middleware degisiklikleri - cuma 14:30"
Dangling Commit’leri Bulma: fsck ile Birlikte Kullanım
Reflog dışında bir alternatif veya tamamlayıcı yöntem: git fsck. Bu komut, hiçbir referans tarafından işaret edilmeyen “dangling” (sallanan) commit’leri bulur:
git fsck --lost-found
dangling commit a1b2c3d4e5f6789...
dangling blob f1e2d3c4b5a6978...
dangling commit b2c3d4e5f6a7890...
Dangling commit’leri daha filtrelenmiş görmek için:
git fsck --no-reflogs | grep "dangling commit"
Bu commit’leri incelemek için:
git show a1b2c3d4e5f6789
Kurtarmak istediğinizi yeni branch’e alın:
git branch kurtarilan-commit a1b2c3d4e5f6789
--lost-found flag’ini kullandıysanız Git ayrıca .git/lost-found/ dizinine kurtarılabilir nesneleri yazar, oraya da bakabilirsiniz.
Merge Sonrası Kurtarma: ORIG_HEAD
Merge veya rebase sonrası pişman olduğunuzda reflog’a gerek bile kalmayabilir. Git bu durumlar için ORIG_HEAD adında özel bir referans tutar:
# Felaket merge işlemi
git merge feature/experimental
# Hemen geri almak için
git reset --hard ORIG_HEAD
ORIG_HEAD reflog’un kısayolu gibi düşünebilirsiniz, merge, rebase ve reset işlemlerinden önceki HEAD pozisyonunu otomatik kaydeder. Ama sadece bir önceki işlemi saklar, dolayısıyla birden fazla adım geri gitmek istiyorsanız yine reflog’a dönmeniz gerekir.
Gerçek Dünya Senaryosu: CI/CD Pipeline Çöktüren Commit
Bunu bizzat yaşadım. Bir Cuma öğleden sonra (evet, hep Cuma öğleden sonra) junior bir geliştirici yanlışlıkla environment variable’ları içeren bir dosyayı commit’e dahil etti ve ardından git push --force ile history’yi temizlemeye çalıştı. Remote’da iz kalmamıştı ama local’de reflog duruyordu.
Önce ne zaman olduğunu bulduk:
git reflog --date=iso | grep "force"
git reflog --all --date=iso | head -30
Sonra o commit’i tespit edip, tam olarak ne değiştiğini inceledik:
git show HEAD@{4}
git diff HEAD@{4} HEAD@{3}
Hassas bilgilerin gerçekten commit’e girip girmediğini anladık (girmişti), sonra git’in geçmişini temizlemek için git filter-branch veya BFG Repo Cleaner kullandık. Ama önce reflog sayesinde tam olarak ne olduğunu anlayabildik.
Önemli ders: git push --force remote’u değiştirir ama local reflog’a dokunmaz. Bir şeyler ters gittiğinde her zaman local’de araştırmaya başlayın.
Reflog Konfigürasyonu ve Saklama Süreleri
Reflog’un ne kadar süre tutulacağını kontrol edebilirsiniz:
# Mevcut ayarları görmek için
git config --list | grep reflog
git config --list | grep gc
Ayarları değiştirmek için:
# Normal commit'ler için saklama süresi (varsayılan 90 gün)
git config gc.reflogExpire 180
# "Unreachable" (hiçbir branch'ten erişilemeyen) commit'ler için (varsayılan 30 gün)
git config gc.reflogExpireUnreachable 60
Global olarak tüm projelerinize uygulamak için:
git config --global gc.reflogExpire "180 days"
git config --global gc.reflogExpireUnreachable "60 days"
Bu değerleri artırmak disk alanı kullanır ama kritik projelerde buna değer. Özellikle büyük ekiplerde çalışıyorsanız ve yanlışlıkla silme olasılığı yüksekse, 180 güne çekmek makul bir önlem.
Reflog’u manuel temizlemek isterseniz (genellikle yapmayın):
# Belirli bir süreden eski kayıtları temizle
git reflog expire --expire=30.days refs/heads/main
# Tüm reflog'u temizle (çok dikkatli olun)
git reflog expire --expire=now --all
Pratikte Reflog Workflow’u
Reflog’u etkili kullanmak için bir zihinsel model önermek istiyorum. Bir şeyler ters gittiğinde şu adımları izleyin:
- Paniklemeni: Git verilerinizi hemen silmez. Garbage collection genellikle manuel tetiklenmez veya uzun zaman periyotlarında çalışır.
- Reflog’u görüntüle:
git reflogile ne zaman ne olduğunu kronolojik olarak okuyun.
- Hedef commit’i tespit et: Hangi adıma dönmek istediğinizi
HEAD@{N}veya hash ile belirleyin.
- Önce incele, sonra uygula:
git show HEAD@{N}ile commit içeriğini gördükten sonra reset veya checkout yapın.
- Geçici branch aç: Doğrudan reset atmak yerine önce
git branch kurtarma-branch HEAD@{N}ile güvenli bir nokta oluşturun, sonra inceleyip karar verin.
# Güvenli kurtarma akışı
git reflog
git show HEAD@{5} # incele
git branch gecici-kurtarma HEAD@{5} # güvenli branch oluştur
git checkout gecici-kurtarma # incele
# Her şey yolunda görünüyorsa:
git checkout main
git reset --hard gecici-kurtarma
git branch -d gecici-kurtarma
Sonuç
Reflog, Git’in en az anlatılan ama en çok ihtiyaç duyulan özelliklerinden biri. Commit geçmişinin silme işlemine karşı bir güvenlik ağı değil, pointer hareketlerinin eksiksiz bir günlüğü. Bu ayrımı anladığınızda neden bu kadar güçlü olduğunu görüyorsunuz.
Ekibinizdeki herkese şunu öğretin: “Git’te bir şeyi commit ettiyseniz, reflog temizlenmeden onu kurtarabilirsiniz.” Bu bilgi, özellikle junior geliştiricilerin “her şeyi mahvettim” krizlerinin önüne geçer.
Son olarak pratik bir not: reflog yalnızca local repository’de çalışır. Remote’ta silinmiş bir şeyi reflog ile kurtaramazsınız (ya birinin local’inden kurtarmanız gerekir, ya da backup’larınıza dönmeniz). Bu yüzden önemli branch’lerde --force-push politikalarını ve repository backup’larını ihmal etmeyin. Reflog güçlü ama sınırlı bir araç, sınırlarını bilerek kullanın.
