git rebase ve git merge arasındaki tercih, çoğu ekipte gereğinden fazla tartışılan ama az anlaşılan bir konudur. İkisi de iki dalın işini birleştirir; fark, geçmişin nasıl göründüğünde ve çatışmaları nasıl yönettiğinizdedir. Bu yazıda her iki komutun ne yaptığını, ne zaman hangisini seçeceğinizi ve ekipçe yaşanan klasik kazaları somut örneklerle ele alıyoruz.
İkisi de aynı işi yapar, ama geçmişi farklı yazar
Diyelim ki main dalından feature dalını açtınız ve siz çalışırken main ilerledi. Şimdi main'deki yeni commit'leri kendi dalınıza almak istiyorsunuz. İki yol var.
Merge, iki dalın uçlarını alıp yeni bir birleştirme commit'i (merge commit) oluşturur. Sizin commit'lerinize dokunmaz; sadece iki tarihi tek bir noktada bağlar:
git checkout feature
git merge main
Rebase ise sizin commit'lerinizi geçici olarak bir kenara koyar, dalın başlangıç noktasını main'in son haline taşır ve commit'lerinizi tek tek o yeni temelin üzerine yeniden uygular:
git checkout feature
git rebase main
Sonuç olarak merge sonrası geçmiş dallanıp birleşirken, rebase sonrası geçmiş düz bir çizgi gibi görünür: sanki siz baştan beri güncel main üzerinde çalışmışsınız gibi.
Rebase commit'leri kopyalar, taşımaz
Burada anlaşılması gereken kritik nokta şu: rebase commit'lerinizi taşımaz, kopyalar. Yeniden uygulanan her commit yeni bir hash alır, çünkü içeriği aynı olsa da artık farklı bir ebeveyne (parent) bağlanmıştır. Eski commit'ler bir süre reflog'da durmaya devam eder, ama dalınız artık yeni kopyalara işaret eder.
Bunun pratik sonucu büyük: rebase, geçmişi yeniden yazar. Henüz kimseyle paylaşmadığınız yerel bir dalda bu tamamen güvenlidir. Ama başkalarının çektiği (pull ettiği) paylaşılan bir dalda yaparsanız, herkesin geçmişi sizinkiyle uyuşmaz hale gelir. Buradan da en önemli kurala geliyoruz.
Altın kural: paylaşılan geçmişi rebase'leme
Pratikte tek bir kuralı hatırlarsanız sorunların çoğundan kaçınırsınız:
- Yalnızca sizin olan, henüz push'lanmamış veya kimsenin temel almadığı dalları gönül rahatlığıyla rebase'leyin.
main,developgibi herkesin paylaştığı dalları asla rebase'lemeyin.
Eğer bir feature dalını rebase'leyip zaten push'ladıysanız, uzak dalı güncellemek için zorla push yapmanız gerekir. Burada düz --force yerine her zaman daha güvenli olanı tercih edin:
git push --force-with-lease
--force-with-lease, uzak daldaki commit en son gördüğünüz halden farklıysa (yani araya başka biri girdiyse) push'u reddeder; böylece bir takım arkadaşınızın işini sessizce ezmezsiniz. Çıplak --force bu güvenliği sağlamaz.
Çatışmalar: tek seferde mi, adım adım mı
Çatışma (conflict) yönetimi ikisinde ciddi şekilde farklıdır. Merge'de tüm değişiklikler tek bir noktada karşılaşır; çatışmaları bir kez çözer, git add yapar ve merge'ü tamamlarsınız.
Rebase'de ise commit'leriniz sırayla yeniden uygulandığı için, çatışma her commit'te ayrı ayrı çıkabilir. Aynı satıra dokunan birden çok commit'iniz varsa, benzer çatışmayı birkaç kez çözmek zorunda kalabilirsiniz. Akış şöyle işler:
# çatışan dosyaları düzelt, sonra:
git add <dosya>
git rebase --continue
# o commit'i atlamak istersen:
git rebase --skip
# her şeyi iptal edip başa dönmek için:
git rebase --abort
İşler karışırsa git rebase --abort sizi rebase öncesi duruma eksiksiz geri götürür; bu yüzden takılırsanız panik yapmadan iptal edip baştan deneyebilirsiniz.
Interaktif rebase ile geçmişi temizleme
Rebase'in en güçlü kullanımı dalları birleştirmek değil, kendi commit geçmişinizi düzenlemektir. git rebase -i (interaktif) ile push'tan önce commit'leri birleştirebilir, sıralayabilir veya mesajlarını düzeltebilirsiniz:
git rebase -i HEAD~4
Açılan editörde her commit için bir komut seçersiniz: pick (olduğu gibi tut), squash (bir öncekiyle birleştir), reword (mesajı değiştir), drop (sil). Böylece "düzeltme", "yine düzeltme", "typo" gibi dağınık commit'leri tek anlamlı bir commit'e indirip pull request'i incelemeye değer hale getirirsiniz. Bunu da yalnızca henüz paylaşmadığınız commit'lerde yapın.
Ekipte pratik bir strateji
Çoğu ekip için iyi işleyen, ikisini birleştiren bir yaklaşım var:
- Feature dalınızı güncel tutmak için
main'i dalınıza rebase'leyin (git rebase main) — geçmiş düz kalır, gereksiz merge commit'leri birikmez. - Feature dalını
main'e alırken merge kullanın, çoğunlukla--no-ffile — böylece "bu özellik buradan girdi" bilgisi tek bir merge commit'inde net durur. - Pull request'i göndermeden önce
git rebase -iile commit'lerinizi düzenleyin.
Birçok ekip ayrıca PR'ları "squash and merge" ile birleştirir: tüm feature tek bir commit olarak main'e iner. Bu, main geçmişini son derece okunur tutar; her satır bir özelliğe karşılık gelir. Hangi stratejiyi seçerseniz seçin, önemli olan ekipçe tutarlı olmaktır — yarısı rebase yarısı merge yapan bir takımın geçmişi en kötüsüdür.
Sık Sorulan Sorular
Rebase merge'den daha mı tehlikeli?
Tehlikeli değil, sadece geçmişi yeniden yazdığı için kuralları var. Yerel, paylaşılmamış dallarda rebase tamamen güvenlidir ve geçmişi temiz tutar. Tek gerçek tehlike, başkalarının kullandığı paylaşılan bir dalı rebase'leyip zorla push'lamaktır; bu kuralı çiğnemediğiniz sürece rahatça kullanabilirsiniz.
Çatışma çıkarsa rebase'i nasıl iptal ederim?
git rebase --abort komutu sizi rebase'e başlamadan önceki duruma eksiksiz geri döndürür. Hiçbir commit'inizi kaybetmezsiniz; kafanız karışınca iptal edip merge ile devam etmek tamamen geçerli bir seçenektir.
Yanlışlıkla geçmişi bozarsam ne yaparım?
git reflog sizin kurtarıcınızdır. Git, dalınızın işaret ettiği her eski konumu bir süre saklar; reflog'da doğru hash'i bulup git reset --hard <hash> ile o ana geri dönebilirsiniz. Bu yüzden "geçmişi yeniden yazmak" göründüğü kadar kalıcı değildir.
Ekibinizin Git akışını sadeleştirmek mi istiyorsunuz? Karmaşık dal stratejilerini, CI/CD entegrasyonunu ve temiz bir commit geçmişini birlikte kuralım. Benimle iletişime geçin, projenize uygun bir iş akışı tasarlayalım.