Git rebase ve merge farkı, ekip içinde en çok tartışılan ama en az açıklanan konulardan biridir. İkisi de "bu daldaki değişiklikleri şu dala getir" işini yapar, sonuçta çalışan dosyalar çoğu zaman birebir aynıdır; fark, o sonuca giden geçmişte ortaya çıkar. Merge bir birleşme noktası bırakır ve olan biteni olduğu gibi kaydeder, rebase ise commit'lerinizi sanki en baştan güncel kodun üzerine yazılmış gibi yeniden oluşturur.
Bu ayrımı bilmemek iki tip kazaya yol açar: ya depoda kimsenin okuyamadığı, birleşme commit'leriyle dolu bir spagetti geçmiş oluşur ya da birisi paylaşılan bir dalı rebase edip beş kişinin yerel deposunu bozar. Bu rehberde iki komutun tam olarak ne yaptığını, hangi senaryoda hangisinin doğru olduğunu, interaktif rebase ile commit temizliğini, çakışma çözmeyi ve bir şey ters gittiğinde git reflog ile nasıl geri döneceğinizi gerçek komutlarla anlatacağım.
Merge Tam Olarak Ne Yapar#
git merge, iki dalın uçlarını alır ve ortak atalarına bakarak yeni bir birleştirme commit'i üretir. Bu commit'in iki ebeveyni vardır; yani geçmişte "burada iki iş kolu birleşti" bilgisi kalıcı olarak yazılıdır. Hiçbir mevcut commit'in kimliği (SHA'sı) değişmez, hiçbir şey yeniden yazılmaz. Merge bu yüzden eklemeli ve güvenli bir işlemdir.
İki özel durum vardır. Hedef dal, kaynak daldan hiç ayrılmamışsa (yani aradan yeni commit girmemişse) Git birleştirme commit'i üretmek yerine dal işaretçisini ileri kaydırır; buna ileri sarma (fast-forward) denir. Bunu istemiyorsanız --no-ff ile her zaman gerçek bir birleştirme commit'i üretmesini zorlayabilirsiniz:
git switch main
git pull --ff-only
# Gerçek birleştirme commit'i üret (grup halinde geri almayı kolaylaştırır)
git merge --no-ff feature/fatura-pdf-cikti -m "feature/fatura-pdf-cikti birleştirildi"
# Yalnızca ileri sarma mümkünse birleştir, değilse hata ver (ortam dalları için ideal)
git merge --ff-only staging
--no-ff tercihinin somut bir faydası vardır: özellik bir hafta sonra sorun çıkarırsa git revert -m 1 <birleştirme-sha> ile o özelliğin tamamını tek komutta geri alabilirsiniz. Fast-forward ile birleştirilmiş on commit'i tek tek geri almak ise sıkıcı ve hataya açıktır. Ortam dallarında ise tam tersini istersiniz: --ff-only sayesinde canlı dal, main'den bağımsız bir commit alamaz.
Merge'ün geçmişe etkisi görsel olarak da nettir:
git log --oneline --graph --decorate -12
# * 9f2c1ab (main) feature/fatura-pdf-cikti birleştirildi
# |\
# | * 71ad4e0 fatura PDF şablonuna KDV satırı eklendi
# | * 4c8b09d PDF üretimi için kuyruk işi yazıldı
# |/
# * 2e5d7f3 ödeme hatırlatma metni güncellendi
O |\ çatalı, işin ayrı bir dalda yapıldığının kalıcı kanıtıdır. Bazı ekipler bunu gürültü sayar, bazıları denetlenebilirlik için değerli bulur. Doğrusu ekibinizin geçmişi ne amaçla okuduğuna bağlıdır.
Rebase Tam Olarak Ne Yapar#
git rebase, dalınızdaki commit'leri tek tek alır, hedef dalın en son hâlinin üzerine yeniden uygular ve her biri için yeni bir commit üretir. Yani orijinal commit'ler silinmez ama dalınızın işaretçisi artık yeni, farklı SHA'lı kopyalara bakar. Sonuç, hiç dallanma olmamış gibi görünen düz (linear) bir geçmiştir.
git switch feature/fatura-pdf-cikti
git fetch origin
# Dalımdaki commit'leri güncel main'in üzerine taşı
git rebase origin/main
# Çakışma çıkarsa: dosyayı düzelt, ekle, devam et
git add config/fatura.php
git rebase --continue
# Vazgeçmek istersen her şey rebase öncesine döner
git rebase --abort
Rebase sonrası dalınız uzaktakinden "farklı" görünür, çünkü commit kimlikleri değişmiştir. Bu yüzden push ederken zorlamak gerekir — ama asla çıplak --force ile değil:
# Yanlış: başkasının bu arada ittiği commit'i sessizce siler
git push --force
# Doğru: uzaktaki dal beklediğim commit'te değilse itmeyi reddeder
git push --force-with-lease
--force-with-lease, uzaktaki dalın sizin en son gördüğünüz hâlde olduğunu doğrular; arkadaşınız bu arada bir şey ittiyse komut hata verir ve veri kaybını önler. Ekip kuralınız "rebase serbest, çıplak force yasak" olmalıdır. Bu kuralı istemci tarafında zorlamak isterseniz Git hooks ile otomasyon yazısındaki pre-push kancası tam bu iş içindir.
İki Yaklaşımı Yan Yana Koymak#
| Ölçüt | Merge | Rebase |
|---|---|---|
| Commit SHA'ları | Değişmez | Yeniden üretilir |
| Geçmiş biçimi | Çatallı, birleşme noktalı | Düz, tek çizgi |
| "Ne zaman entegre oldu" bilgisi | Korunur | Kaybolur |
| Paylaşılan dalda güvenli mi | Evet | Hayır |
| Çakışma çözme sayısı | Bir kez, birleşme anında | Her commit için ayrı ayrı olabilir |
| Grup hâlinde geri alma | git revert -m 1 ile kolay | Commit commit uğraşmak gerekir |
git bisect ile hata avı | Birleşme commit'leri gürültü yapar | Düz geçmişte çok rahat |
| Tipik kullanım | Dalı main'e almak | Kişisel dalı güncel tutmak |
Tablodaki en çok gözden kaçan satır çakışma sayısıdır. Merge'te çakışmayı bir kez, son hâller üzerinden çözersiniz. Rebase'te ise Git commit'leri sırayla uyguladığı için aynı dosya çakışması birkaç commit boyunca tekrar tekrar karşınıza çıkabilir. Bunu azaltmanın iki yolu vardır: dalı sık sık rebase edip küçük tutmak ve Git'in çakışma çözümlerini hatırlamasını sağlamak:
# Aynı çakışmayı daha önce nasıl çözdüğümü hatırla ve otomatik uygula
git config --global rerere.enabled true
rerere (reuse recorded resolution) uzun rebase seanslarında gerçekten hayat kurtarır ve nedense çok az kişi açıktır.
Hangi Senaryoda Hangisi#
Kararı basitleştiren tek bir soru vardır: bu commit'leri benden başka biri gördü mü? Görmediyse rebase serbesttir. Gördüyse merge kullanın.
- Kişisel dalınızı güncel tutarken → rebase.
mainilerledi ve siz onun üzerine oturmak istiyorsunuz.git pull --rebasebunu tek adımda yapar ve gereksiz "Merge branch main into feature" commit'lerini engeller. Bunu kalıcı hâle getirin:git config --global pull.rebase true. - Özellik dalını
main'e alırken → merge. Burada amaç geçmişi temizlemek değil, entegrasyon anını kaydetmektir. Pull request arayüzlerindeki "squash and merge" seçeneği de aslında bunun bir çeşididir: dalın tüm commit'lerini tek bir commit'e indiripmain'e ekler. - Ortam dalını ilerletirken →
--ff-onlymerge. Canlı dalınmain'den ayrılmasını yapısal olarak engeller. Bu düzenin nasıl kurulduğunu Git branch stratejileri yazısında ayrıntılı anlattım. - Yayınlanmış bir commit'i geri alırken → ne rebase ne merge,
git revert. Revert, hatalı değişikliği tersine çeviren yeni bir commit üretir ve geçmişi bozmaz. Paylaşılan dalda geri alma yönteminiz her zaman budur. - İnceleme öncesi commit'lerinizi düzenlerken → interaktif rebase. Bir sonraki bölümün konusu.
İnteraktif Rebase ile Commit Temizliği#
Çalışırken commit'ler dağınık olur: "wip", "düzeltme", "yine düzeltme", "typo". Bunları incelemeye göndermeden önce toparlamak, hem inceleyene saygıdır hem de gelecekte git log okuyan kişiye. git rebase -i bunun aracıdır.
# Son 5 commit'i düzenlemek için
git rebase -i HEAD~5
Açılan editörde her satırın başındaki komutu değiştirirsiniz:
pick 4c8b09d PDF üretimi için kuyruk işi yazıldı
squash 71ad4e0 wip
squash a903f21 yine wip
reword 5b1c0de fatura sablonu
drop ff20a4c gecici debug ciktisi
pick: commit olduğu gibi kalır.squash/s: bir önceki commit ile birleşir, mesajları birleştirmeniz istenir.fixup/f:squashgibi birleşir ama mesajı atar — "typo düzeltmesi" commit'leri için idealdir.reword/r: içerik aynı kalır, yalnızca mesaj düzenlenir.drop/d: commit tamamen çıkarılır.edit/e: o commit'te durur, dosyaları değiştirip devam edebilirsiniz.
Düzenli bir iş akışında fixup commit'lerini otomatikleştirebilirsiniz:
# Hangi commit'i düzelttiğinizi işaretleyin
git commit --fixup 4c8b09d
# Sonra hepsini otomatik doğru yere yerleştirin
git rebase -i --autosquash HEAD~10
Temiz commit mesajları yalnızca estetik değildir; otomatik sürüm numarası ve changelog üretimi doğrudan bu mesajları okur. Yapıyı standartlaştırmak için conventional commits ve semantic versioning yazısındaki şemayı kullanabilirsiniz.
Bir Şey Ters Gittiğinde: reflog ile Kurtarma#
Rebase'ten korkulmasının sebebi geri dönülemez sanılmasıdır; oysa Git neredeyse hiçbir şeyi hemen silmez. git reflog, HEAD'in nereden nereye gittiğinin yerel günlüğüdür ve varsayılan olarak 90 gün saklanır.
git reflog -10
# 9f2c1ab HEAD@{0}: rebase (finish): returning to refs/heads/feature/fatura-pdf-cikti
# 71ad4e0 HEAD@{1}: rebase (pick): PDF kuyruk işi
# c40d19e HEAD@{2}: checkout: moving from main to feature/fatura-pdf-cikti
# 8ab73f5 HEAD@{3}: commit: fatura şablonuna KDV satırı
# Rebase öncesi hâle geri dön
git reset --hard HEAD@{3}
# Ya da güvenli yol: eski hâli ayrı bir dala al, sonra karşılaştır
git branch kurtarma-yedegi HEAD@{3}
Uzun bir rebase'e girmeden önce bir güvenlik dalı açmak da iyi bir alışkanlıktır: git branch yedek/fatura-pdf komutu bir saniye sürer ve bütün stresi ortadan kaldırır. Unutulan tek şey şudur: reflog yereldir. Bir başkasının makinesinde bozduğunuz geçmişi sizin reflog'unuz kurtarmaz; bu yüzden paylaşılan dallarda kural değişmez.
Sık Yapılan Hatalar ve Tuzaklar#
Paylaşılan dalı rebase etmek. main, develop veya birden fazla kişinin çalıştığı bir özellik dalını rebase edip force push etmek, herkesin yerel geçmişini kopukluğa düşürür. Ekipteki diğer kişiler git pull yaptığında birbirinin kopyası commit'ler oluşur ve depo gerçekten karışır. Kural nettir: yalnızca sizin ittiğiniz, kimsenin üzerine iş yapmadığı dalı rebase edin.
Çıplak --force kullanmak. Aradaki farkı görmezden gelmeyin: --force-with-lease beklenmedik bir uzak commit varsa durur, --force onu sessizce siler. Bunu ekip kuralı yapın, hatta CI tarafında main için force push'u tamamen kapatın.
git pull alışkanlığını sorgulamamak. Varsayılan git pull, uzak değişiklikleri merge eder ve dalınızda anlamsız birleşme commit'leri biriktirir. pull.rebase true ayarı bu gürültüyü tamamen ortadan kaldırır ve dalınızı main'in üzerinde temiz tutar.
Çakışmayı yanlış tarafa çözmek. Rebase sırasında "ours" ve "theirs" kavramları tersine döner: rebase ederken ours hedef dal (yani main), theirs sizin commit'inizdir. Merge'te ise tam tersi. Otomatik çözüm bayraklarını (-X ours gibi) ezberden kullanmak yerine, çakışan dosyayı elle açıp okumak her zaman daha güvenlidir.
Sonucu test etmeden itmek. Rebase, her commit'i yeniden uyguladığı için ara commit'lerden bazıları derlenmeyebilir. Rebase bittikten sonra en azından bir kez test paketini çalıştırın; sürekli entegrasyonda git bisect ile hata aradığınızda bozuk ara commit'ler işinizi çok zorlaştırır.
Sıkça Sorulan Sorular#
Rebase mi merge mi kullanmalıyım#
Kararı verecek soru şudur: bu commit'ler uzak depoya gidip başkası tarafından çekildi mi? Çekilmediyse rebase ile dalınızı güncel ve temiz tutabilirsiniz. Çekildiyse merge kullanın, çünkü rebase o commit'lerin kimliğini değiştirerek diğer herkesin geçmişini bozar. Yaygın ve sağlıklı düzen şudur: kişisel dalı rebase ile güncelle, main'e ise merge ya da squash merge ile al.
Rebase geçmişi siler mi, veri kaybeder miyim#
Rebase eski commit'leri silmez, onların yeni kopyalarını üretir ve dal işaretçisini kopyalara taşır. Orijinaller bir süre git reflog üzerinden erişilebilir kalır ve git reset --hard HEAD@{n} ile geri dönebilirsiniz. Gerçek veri kaybı yalnızca çıplak git push --force ile başkasının commit'ini uzakta ezdiğinizde olur; bu yüzden her zaman --force-with-lease kullanın.
git pull --rebase ne işe yarar#
Uzaktaki değişiklikleri çeker ve sizin yerel commit'lerinizi onların üzerine yeniden uygular; böylece "Merge branch main" tipinde gereksiz birleşme commit'leri oluşmaz. Dalınız her zaman main'in en güncel hâlinin üzerinde durur ve pull request diff'i yalnızca sizin değişikliklerinizi gösterir. Kalıcı yapmak için git config --global pull.rebase true komutunu bir kez çalıştırmanız yeterlidir.
Rebase sırasında çakışmayı nasıl çözerim#
Git durur ve çakışan dosyaları işaretler. Dosyayı açıp doğru içeriği bırakın, git add <dosya> ile hazırlayın ve git rebase --continue ile devam edin. O commit'i tamamen atlamak isterseniz git rebase --skip, her şeyden vazgeçip başlangıç durumuna dönmek isterseniz git rebase --abort kullanın. Aynı çakışmanın tekrar tekrar çıkmasını önlemek için git config --global rerere.enabled true ayarını açın.
Squash merge ile rebase aynı şey mi#
Değil. Rebase, dalınızdaki commit'leri teker teker hedefin üzerine taşır ve commit sayısı korunur. Squash merge ise dalın tüm değişikliklerini tek bir commit'e sıkıştırarak hedef dala ekler; ara commit'ler hedef dalın geçmişinde görünmez. Squash merge, main geçmişini "her satır bir özellik" biçiminde okunur tutmak isteyen ekipler için pratik bir tercihtir.
Rebase edilmiş bir dalı ekip arkadaşım nasıl güncellemeli#
Rebase edilmiş dalı çeken kişi normal git pull ile ilerleyemez, çünkü geçmiş yeniden yazılmıştır. Yerelde kendi commit'i yoksa en temizi git fetch origin ardından git reset --hard origin/<dal> komutudur. Yerelde henüz itmediği commit'ler varsa önce onları git branch yedek ile saklamalı, sonra sıfırlayıp git cherry-pick ile geri taşımalıdır. Bu zahmet, paylaşılan dalları neden rebase etmediğimizin de kanıtıdır.
Kapanış#
Rebase ile merge arasındaki seçim bir zevk meselesi değil, sahiplik meselesidir. Kendi dalınızda geçmişi istediğiniz gibi düzenleyebilirsiniz; paylaşılan geçmiş ise ortak maldır ve yalnızca eklemeli işlemlerle değiştirilir. Aklınızda dört alışkanlık kalsın: kişisel dalı pull --rebase ile güncel tut, incelemeden önce commit'leri rebase -i --autosquash ile toparla, itmek gerektiğinde daima --force-with-lease kullan ve bir şey bozulduğunda paniklemeden git reflog'a bak.
Bu akışları otomatikleştirmek için bir CI koşucusuna, hazırlık ortamına ve dağıtım hedefine ihtiyacınız olur. Kendi Git ve CI altyapınızı barındırmak isterseniz tam root erişimli VDS ve sanal sunucu paketlerimiz, işletim yükünü bize bırakmak isterseniz sunucu yönetimi hizmetimiz bu iş için uygundur. Küçük projelerde depodan doğrudan yayına geçmek istiyorsanız web hosting paketlerimizdeki Git desteği de çoğu senaryoyu karşılar.