Git Merge Stratejeleri ve Farkları
9 min read

Git Merge Stratejeleri ve Farkları

Temiz, anlaşılır ve düzenli bir git history için hangi merge stratejisini seçeceğimiz önemli bir karardır. Bu yazıda tüm stratejeleri detaylı inceliyoruz.
Git Merge Stratejeleri ve Farkları

Git, ekiplerin verimli çalışabilmesi için farklı merge stratejileri sunar. Doğru stratejiyi seçmek, projenin commit geçmişini temiz ve anlaşılır tutarken aynı zamanda çalışma sürecini de optimize eder. Bu yazıda, Git'in sunduğu tüm merge stratejilerini açıklayacak ve hangi durumlarda kullanılmalarının en iyi olduğunu ele alacağız.

Development süreçlerimizde git branchleri oluşturuyoruz, feature, bug fix ve onlarcası için commitlerimizi atıyoruz ve pull requestler veya direkt master pushlar ile işleri tamamlayıp sıradaki işe geçiyoruz. Peki arkada bırakılan çöplüğün farkında oluyor musunuz?

İlk olarak en sık karşılaştığım ve nefret ettiğim bir history tipini göstermek istiyorum. Arka arkaya atılmış onlarca fix, refactor gibi hiçbir anlam ifade etmeyen commitler. Nefretimin sebebi sadece commit mesajları değil, aynı zamanda git history'nin amaçsızca kabartılıp bir deneme tahtası gibi kullanılması.

Görsel-1: Yüzlerce arka arkaya atılmış bir history'nin sadece ekrana sığan bir kısmı

Diğer bir kötü history çeşidi de hangi branchin nereden çıkıp nereye girdiğinin bulmanın, commitleri takip etmenin çok zor olduğu merge stratejisi. Üstelik üstteki görseldekiyle birleşince bir branchi takip etmek casus oyununa dönüyor.

Görsel-2: Ara bul ipin ucunu

Mergelenmiş fakat silinmemiş veya test için oluşturulup öylece unutulmuş branchleri de es geçmeyeceğim. Zaten mergelediğiniz branchi neden halen tutarsınız ki? Artık o kadar çöp branch oluşuyor ki, remote branchlerden hangisinin devam eden, hangisinin tamamlanan iş olduğunu bulmak için harcadığımız süreye yazık!

Görsel-3: Mergelendiği halde duran, test için açılıp öylece bırakılan branchlerin ve manasız commit mesajlarının bir arada olduğu göz kanatan bir kesit

Neden böyle olduğunun sebebi ise çok basit:

  1. İnsanların merge stratejelerinden haberi yok, PR açıp mergeleyince herşeyin bittiğini düşünüyorlar.
  2. Görsellerdeki gibi bir git history görmeyenler dahi var. Terminalden git komutları kullanarak işlerini yürütüyorlar. Arkada bıraktıkları yığının asla farkında değiller.
  3. Attıkları commitlerin çok acil olduğunu düşünenler. Onlara göre bu aciliyet git history'nin dağılmasından daha önemli. Peki neden sonradan squashlamıyor veya toparlayamıyorsunuz dağıttınız bu alanı?
  4. Mergeledikten sonra source branchi çeyizine götürmek isteyenler. Mergelerken source branchi de sil seçeneğini illa zorunlu mu kılalım? Bence zorunlu olmalı bu arada!

Sebep ararsak daha çok çıkar ama içimdekileri yeterince haykırdıktan sonra gelin artık şu git historylerini bir düzene oturtalım. Bunun için de ilk olarak hangi stratejiler var görelim.


1. Merge Stratejeleri

Bu başlık altında 7 farklı merge stratejisini inceleyeceğiz. Her birinin nasıl çalıştığına, diğerinden farkına ve önemli ayrıntılarına değineceğiz. Fakat bu 7 farklı strateji temelde 4 ana işlemin farklı varyasyonlarını içerir. Bu nedenle bu 4 ana işleme aşağıdaki animasyondan bir göz atarak başlayalım.

  • Fast-Forward Merge: Eğer hedef branch'te (genellikle main veya master) yeni bir commit yoksa, feature branch'ini doğrudan onun üzerine taşıyarak birleştirir. Bu yöntem, branch geçmişini temiz tutar ancak merge commit’i oluşturmaz.
  • Merge: İki branch arasında birleştirme işlemi yaparak yeni bir merge commit oluşturur. Bu yöntem, branch'lerin ayrı bir geçmişe sahip olmasını korur ve tüm commitleri olduğu gibi saklar.
  • Squash: feature branch'inde yapılan tüm commitleri tek bir commit haline getirerek hedef branch'e birleştirir. Geçmişi daha temiz ve düzenli tutmak için kullanılır, ancak tek tek commit bilgileri kaybolur.
  • Rebase: feature branch'indeki commitleri, hedef branch'in (main veya master) en güncel halinin üzerine taşıyarak yeniden oynatır. Merge commit oluşturmaz ve daha temiz, lineer bir commit geçmişi sağlar.

1.1. Merge Commit (--no-ff)

Kaynak branch zaten hedef brach ile güncel olsa bile her zaman yeni bir merge commit oluşturur ve hedef branchi buna göre günceller.

Özellikleri:

  • Her zaman yeni bir merge commit oluşturur.
  • Tüm commit geçmişini korur.
  • Branch'in nereden çıktığını ve ne zaman birleştiğini net bir şekilde gösterir.
  • Kaynak branchteki commit geçmişi git history'den kaybolmaz.

Avantajları Neler?

  • Bir branchteki commitler ve süreçler net bir şekilde görünür.
  • Bir branchin nereden çıkıp nereye girdiği belli olur.

Sorunları Neler?

  • Branchteki commit sayısı herhangi bir nedenle fazla olduğunda (Görsel-1) takip zorlaşır ve history kirlenir.
  • Eş zamanlı branch sayısının çok olduğu repolarda history i takip etmek zorlaşır

Komut:

 git merge --no-ff feature-branch

1.2. Fast-Forward (--ff)

Kaynak branch ile hedef branch ile güncel değilse, bir merge commit'i oluşturur. Aksi takdirde, hedef branchi kaynak branchteki en son commit'e günceller.

Özellikleri:

  • git merge komutunun varsayılan davranışıdır. Hiç parametre geçilmediğinde ff stratejisi uygulanır.
  • Eğer hedef branch, kaynak branchin en son haliyle fast-forward yapabilecek durumdaysa, ff gerçekleşir ve kaynak branchin tüm commit’leri doğrudan hedef branche eklenir.
  • Eğer hedef branch, kaynak branchin commit’lerini doğrudan üzerine alıp devam edemiyorsa (yani başka commit’ler eklendiyse ve fast-forward mümkün değilse), merge commit atılır
  • Kaynak dalın tüm commit’leri doğrudan hedef dala eklenir.
  • Eğer fast-forward yapılırsa, kaynak branchteki commit geçmişi git history'den kaybolur. Yapılmazsa kaybolmaz.

Fast-forward şartının nasıl sağlanıp sağlanmadığını iki örnek üzerinden görelim.

Örnek-1:

main branchte en son commit A, feature-branch dalında ise A -> B -> C var.

main:            A
feature-branch:  A → B → C
  • main branch, feature-branchın eski bir versiyonunda olduğu için fast-forward mümkün.
  • git merge --ff feature-branch komutunu çalıştırırsanız main branchi B ve C commit’lerini doğrudan üzerine alır.
main:            A → B → C  (Fast-forward merge başarılı)

Örnek-2:

main branchte A commit’i vardı, ancak feature-branch oluşturulduktan sonra main branche X adında yeni bir commit eklendi.

main:            A → X
feature-branch:  A → B → C
  • Şimdi git merge --ff feature-branch çalıştırırsanız fast-forward mümkün değil, çünkü main branchi X commit’ini içeriyor.
  • Bu yüzden merge commit oluşturulur

Ne Zaman Kullanılır?

  • Kısa ömürlü, tek başına geliştirilen feature branchlerde tercih edebilirsiniz.
  • Commit geçmişinin mümkün olduğunda temiz kalması istendiğinde kullanılır.

Komut:

 git merge --ff feature-branch

1.3. Fast-Forward only (--ff-only)

Kaynak branch, hedef branch ile güncel değilse, merge requesti reject eder. Aksi takdirde, hedef branchi kaynak branchteki en son commit'e günceller.

Özellikleri:

  • Merge işlemi yalnızca fast-forward mümkünse gerçekleştirilir.
  • Eğer fast-forward mümkün değilse işlem reddedilir.
  • Kaynak branchteki commit geçmişi git history'den kaybolur.

Ne Zaman Kullanılır?

  • Merge commit eklenmesini istemediğiniz durumlarda kullanabilirsiniz.
  • Yanlışlıkla büyük değişiklikleri birleştirmemek için kontrollü bir süreç gerektiğinde tercih edilmelidir.

Komut:

 git merge --ff-only feature-branch

1.4. Rebase and Fast-Forward (rebase + merge —-ff-only)

Fast-Forward --ff-only nin çalışabilmesi için yukarıda Örnek-1 de verdiğimiz senaryodaki gibi iki branchin güncel olması gerekir. Eğer Örnek-2 deki gibi sonradan araya commitler girmişse fast-forward yapılamaz.

Bu durumu çözmek için öncelikle rebase ile branch güncel hale getirilir.

Ardından fast-forward yapılır.

Başka bir görselle bir arada özetlersek, brachteki commitleri önce hedef branche rebase eder, ardından hedef branchi kaynak branchteki en son commit'e günceller.

Özellikleri:

  • Fast-Forward only (--ff-only) ile tek farkı, öncesinde rebase yapmasıdır. Böylece fast-forward yapılamama ihtimali çözülmüş olur.
  • Rebase edildiği için commit hashleri değişir.
  • Kaynak branchteki commit geçmişi git history'den kaybolur.

Ne Zaman Kullanılır?

  • Commit geçmişinin tamamen linear (düz bir çizgi) olmasını istiyorsanız kullanmalısınız.

Komut:

 git rebase main
 git checkout main
 git merge --ff-only feature-branch

1.5. Rebase and Merge (rebase + merge --no-ff)

Bir üsttekinden tek farkı son aşamada fast-forward yerine merge commit oluşturmasıdır. Yine de özetleyecek olursak:

Önce rebase edilir.

Ardından merge commit oluşturulur.

Özellikleri:

  • Kaynak branchteki commit’leri hedef branchteki en güncel commit’ten sonra yeniden işletir (rebase yapar).
  • Merge işlemi ekstra bir merge commit ekleyerek tamamlanır.
  • Kaynak branchteki commit geçmişi git history'den kaybolmaz.

Ne Zaman Kullanılır?

  • Geçmişin düzenli ve temiz kalmasını isterken, merge commit’in korunmasını istediğinizde.
  • Büyük ekiplerin çalıştığı projelerde, commit geçmişinin dallanma noktalarını göstermek için.

Komut:

 git rebase main
 git checkout main
 git merge --no-ff feature-branch

1.6. Squash (--squash)

Tüm commit'leri hedef branchte yeni bir non-merge commit'te birleştirir.

Bunu yaparken önce branchteki tüm commitleri squashlayarak tek bir commit haline getirir.

Ardından kaynak branche mergeler.

Özellikleri:

  • Tüm commit’leri tek bir commit haline getirir.
  • Merge commit eklemez.

Ne Zaman Kullanılır?

  • Küçük ve sık commit’lerle çalışıyorsanız ve geçmişi temiz tutmak istiyorsanız kullanmalısınız.
  • PR’lar (Pull Request) birleştirilirken gereksiz commit’leri temizlemek için kullanılır.
  • Kaynak branchteki commit geçmişi git history'den kaybolur.

Komut:

 git merge --squash feature-branch

1.7. Squash, Fast-Forward Only (--squash --ff-only)

Bir üstteki başlıkla olan tek farkı; kaynak branch, hedef branch ile güncel değilse merge requesti reject etmesidir.

Buradaki önemli fark, git merge --squash, main branchinde feature branchinden sonra commit'ler olsa bile çalışır. Ancak, --ff-only seçeneği olduğunda işlem gerçekleştirilmez.

Örneğin:

Aşağıdaki durumda --ff-only seçeneği çalıştırdığınızda hata alırsınız.

Main:  A -- B -- C -- F
                 \
Feature:          D -- E

Fakat sadece --squash seçeneği, branch güncel değilse bile squashlayarak kaynak branche squashlanan commiti mergeler.

Main:  A -- B -- C -- F -- G (G, D ve E değişikliklerini içeren tek bir commit)

Ne Zaman Kullanılır?

  • Commit geçmişini olabildiğince temiz tutmak istediğinizde.
  • Kontrollü bir merge süreci sağlamak için.

Komut:

 git merge --squash --ff-only feature-branch

2. Özet Tablo

Tüm başlıkları bir tabloya dökecek olursak aşağıdaki gibi bir özet tablo elde edebiliriz.

StratejiCommit Geçmişi KorunurMerge Commit EklenirNe Zaman Kullanılır?
Merge Commit (--no-ff)✅ Evet✅ EvetBüyük projelerde, ekip çalışmalarında
Fast-Forward (--ff)✅ Evet❌ HayırKısa ömürlü özellik dallarında
Fast-Forward Only (--ff-only)✅ Evet❌ HayırManuel merge commit eklenmesini önlemek için
Rebase and Merge (rebase + merge --no-ff)✅ Evet✅ EvetTemiz geçmiş istenen büyük projelerde
Rebase and Fast-Forward (rebase + merge --ff-only)✅ Evet❌ HayırTamamen linear bir commit geçmişi için
Squash (--squash)❌ Hayır❌ HayırKüçük commit’leri tek bir commit’e sıkıştırmak için
Squash, Fast-Forward Only (--squash --ff-only)❌ Hayır❌ HayırFast-forward mümkünse commit’leri birleştirmek için

3. Hangisini Kullanmalıyım?

Doğru merge stratejisini seçmek, projenin commit geçmişinin düzenli ve sürdürülebilir olmasını sağlar.

Ekip olarak çalışıyorsanız ve her branchin birleşme noktasının görünmesini istiyorsanız merge commit (--no-ff) veya rebase and merge (rebase + merge --no-ff) kullanabilirsiniz. Fakat bu durumda history uzun ve karışık olacaktır.

Eğer geçmişin olabildiğince temiz kalmasını istiyorsanız rebase and fast-forward (rebase + merge --ff-only) veya squash (--squash) tercih edebilirsiniz. Böylece çatallanmalardan, çıkıp giren branchler görmezsiniz.

Ben hangisini tercih ediyorum?

  • İlk olarak kendi branchimdeki birbiriyle ilişkili commitleri squashlayarak işlem adımlarını gösteren, az ve öz sayıda commit bırakıyorum.
  • Ardından eğer giriş ve çıkış noktaları önemliyse Rebase and Merge (rebase + merge --no-ff), önemli değilse dümdüz bir history görmek için Squash (--squash) tercih ediyorum.

Böylece tüm repo history si tertemiz ve anlaşılır hale geliyor. Yapılan işler, harcanan eforlar gözle görülür hale gliyor ve geçmişte aradığım bir commiti çok kolay bulabiliyorum.

Siz de hangi yöntemin sizin için en uygun olduğuna karar verirken projenizin büyüklüğünü, ekip yapınızı ve commit geçmişinizi nasıl yönetmek istediğinizi göz önünde bulundurarak kara verebilirsiniz..

Şu makaleyi de okunacak güzel bir kaynak olarak bırakıyorum.

What’s the best GitHub pull request merge strategy?
Discover the best GitHub pull request merge strategy for your projects with our comprehensive guide tailored to enhance your workflow.