← Kursa Dön
📄 Text · 30 min

Merge İşlemleri

Giriş: Neden Merge Öğreniyoruz?

Branch açmayı öğrendin. Harika. Ama branch'in değeri, ana projeye geri birleştirildiğinde ortaya çıkar. Yoksa sonsuza kadar ayrı kalan dallar bir işe yaramaz.

Merge (birleştirme), Git'in en kritik ve bazen en karmaşık işlemidir. İki farklı geliştirme yolunu tek bir çizgide buluşturur. Bu derste merge'ün farklı türlerini, her birinin ne zaman gerçekleştiğini ve arka planda neler olduğunu öğreneceğiz.


🎬 Analoji: Nehirlerin Birleşmesi

İki nehir düşün. Bir noktada ayrılıyorlar — biri sola, biri sağa gidiyor. Her biri kendi yolunda farklı mineraller topluyor, farklı taşları aşındırıyor. Sonra tekrar birleşiyorlar. Birleşme noktasında her iki nehrin getirdikleri bir araya geliyor.

Git merge tam olarak bu: iki ayrı geliştirme akışının birleşme noktası.

           ┌── feature ──┐
           │              │
───●───●───●              ●───●───●───►
           │              │
           └──── (ayrılma) ──── (birleşme - merge)

Ama birleşmenin nasıl gerçekleştiği, nehirlerin durumuna göre değişir. Git'te üç tür merge var.


Merge Türleri

1. Fast-Forward Merge

En basit merge türü. "Aslında merge'e bile gerek yok" durumu.

Ne zaman olur? Hedef branch (genelde main) ile kaynak branch arasında ayrışma yoksa. Yani main dalında, branch oluşturulduktan sonra hiçbir yeni commit yapılmamışsa.

ÖNCESI:

main:    [A] ← [B] ← [C]
                        ▲
                      main, HEAD

feature:              [C] ← [D] ← [E]
                                    ▲
                                  feature

main, C'den beri hiç ilerlememiş.
feature, C'nin üzerine D ve E eklemiş.

MERGE:
$ git checkout main
$ git merge feature

SONRASI (Fast-Forward):

main:    [A] ← [B] ← [C] ← [D] ← [E]
                                    ▲
                                  main, HEAD
                                  feature

Sadece main pointer'ı ileri taşındı.
Yeni commit oluşmadı. main = feature.

Pratik uygulama:

# Proje oluştur
$ mkdir merge-lab && cd merge-lab && git init
$ echo "v1" > app.txt
$ git add . && git commit -m "A: İlk commit"

$ echo "v2" >> app.txt
$ git add . && git commit -m "B: Güncelleme"

# Feature branch oluştur ve commit yap
$ git switch -c feature/new-page
$ echo "yeni sayfa" > page.html
$ git add . && git commit -m "D: Yeni sayfa eklendi"

$ echo "sayfa içeriği" >> page.html
$ git add . && git commit -m "E: Sayfa içeriği"

# main'e dön ve merge et
$ git switch main
$ git merge feature/new-page
Updating b2c3d4e..e6f7g8h
Fast-forward              ← Git bunu söylüyor!
 page.html | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 page.html

# Log'a bak — düz çizgi, merge commit yok
$ git log --oneline --graph
* e6f7g8h (HEAD -> main, feature/new-page) E: Sayfa içeriği
* d5e6f7g D: Yeni sayfa eklendi
* b2c3d4e B: Güncelleme
* a1b2c3d A: İlk commit

Fast-forward avantajları:

  • Temiz, düz geçmiş — ek commit yok

  • Basit ve hızlı

Fast-forward dezavantajları:

  • Branch'in varlığı geçmişte görünmüyor — "bu commit'ler bir feature branch'inde mi yapıldı?" belli değil

Fast-Forward'ı Zorla veya Engelleme

# Fast-forward olsa bile merge commit oluştur
$ git merge --no-ff feature/new-page
# Merge commit oluşur — branch geçmişi korunur

# Sadece fast-forward ise merge et, değilse hata ver
$ git merge --ff-only feature/new-page
# Bu, main'in temiz kalmasını garanti eder

2. Three-Way Merge (3-Yollu Merge)

Ne zaman olur? Her iki branch'te de yeni commit'ler varsa. Yani ayrışma var.

ÖNCESI:

              [F]
             / ▲
[A] ← [B] ← [C]  main, HEAD
              \
               [D] ← [E]
                       ▲
                     feature

main'de F commit'i var (C'den sonra).
feature'da D ve E commit'leri var (C'den sonra).
İki yol ayrıldı — fast-forward yapılamaz.

MERGE:
$ git checkout main
$ git merge feature

SONRASI (3-Way Merge):

              [F]───────┐
             /           \
[A] ← [B] ← [C]         [G]  ← Merge Commit
              \           / ▲
               [D] ← [E]─┘  main, HEAD

G = merge commit. İki parent'ı var: F ve E.

Git, birleştirme yapmak için üç nokta kullanır (bu yüzden "3-way"):

  1. Common ancestor (ortak ata): C — iki branch'in ayrıldığı nokta

  2. main'in ucu: F

  3. feature'ın ucu: E

Git, C'deki durumu referans alarak F ve E'deki değişiklikleri birleştirir.

# Pratik örnek
$ mkdir three-way && cd three-way && git init
$ echo "satır 1" > dosya.txt
$ git add . && git commit -m "A: İlk commit"

# Feature branch
$ git switch -c feature/update
$ echo "satır 2 (feature)" >> dosya.txt
$ git add . && git commit -m "D: Feature değişikliği"

# Main'e dön ve farklı bir değişiklik yap
$ git switch main
$ echo "header eklendi" > header.txt
$ git add . && git commit -m "F: Header eklendi"

# Merge
$ git merge feature/update
# Editör açılır — merge commit mesajı:
# "Merge branch 'feature/update'"

# Log'a bak — dallanma ve birleşme görünür
$ git log --oneline --graph
*   g7h8i9j (HEAD -> main) Merge branch 'feature/update'
|\
| * d5e6f7g (feature/update) D: Feature değişikliği
* | f6g7h8i F: Header eklendi
|/
* a1b2c3d A: İlk commit

Merge Commit'in Özelliği:

$ git cat-file -p HEAD
tree abc123...
parent f6g7h8i...    ← 1. parent (main'in son commit'i)
parent d5e6f7g...    ← 2. parent (feature'ın son commit'i)
author Tolgahan Kaya <email> ...
committer Tolgahan Kaya <email> ...

Merge branch 'feature/update'

İki parent! Bu, merge commit'i sıradan commit'lerden ayırır.

3. Merge Commit (--no-ff ile zorunlu)

Fast-forward mümkün olsa bile, branch geçmişini korumak için merge commit oluşturabilirsin:

$ git merge --no-ff feature/about
--no-ff ile:

              ┌── [D] ← [E] ──┐
              │                 │
[A] ← [B] ← [C]              [M]  ← Merge commit
                                ▲
                              main

Fast-forward ile:

[A] ← [B] ← [C] ← [D] ← [E]
                             ▲
                           main

--no-ff, branch'in varlığını geçmişte korur.
"Bu commit'ler bir feature branch'inde yapıldı" görünür.

💡 İpucu: Birçok ekip --no-ff kullanmayı tercih eder. GitHub ve GitLab'da "Create a merge commit" seçeneği bu. Neden? Çünkü: - Hangi commit'lerin bir feature'a ait olduğu belli olur - Merge commit'i revert etmek, tüm feature'ı geri almak demektir - git log --first-parent ile sadece merge'leri görebilirsin


Merge Stratejileri

Git, farklı durumlar için farklı merge stratejileri kullanır. Çoğu zaman bunu bilmene gerek yok — Git otomatik seçer. Ama bilmek faydalı:

Recursive (Varsayılan)

Çoğu 3-way merge durumunda kullanılır. İki branch'in ortak atasını bulur ve akıllıca birleştirir.

$ git merge feature
# Varsayılan olarak recursive strateji kullanılır

Ort (Git 2.33+)

Recursive'in yerini alan yeni, daha hızlı strateji. Büyük merge'lerde %500'e kadar hızlanma sağlayabilir.

$ git merge -s ort feature
# veya Git 2.34+ default olarak ort kullanır

Ours

Merge commit oluşturur ama karşı branch'in değişikliklerini tamamen yok sayar. Nadir kullanılır.

$ git merge -s ours feature
# Merge oldu ama feature'ın hiçbir değişikliği alınmadı
# Sadece "merge ettik" kaydı düştü

Merge'ün Arka Planı: Git Ne Yapar?

Merge işlemi sırasında Git'in adımları:

1. Common Ancestor (Ortak Ata) Bul
   ├── İki branch'in ayrıldığı noktayı bul
   └── git merge-base main feature → commit C

2. Üçlü Karşılaştırma Yap
   ├── C → main: "main'de ne değişmiş?"
   └── C → feature: "feature'da ne değişmiş?"

3. Otomatik Birleştirme
   ├── Farklı dosyalar değişmiş → sorun yok, ikisini de al
   ├── Aynı dosyanın farklı kısımları → sorun yok, ikisini de al
   └── Aynı dosyanın aynı kısımları → CONFLICT! (elle çöz)

4. Sonuç
   ├── Başarılı → merge commit oluştur
   └── Conflict → sana sor, çöz, sonra commit'le

Otomatik Birleştirme Örnekleri

Senaryo 1: Farklı dosyalar değişmiş ✅
main:    index.html değişti
feature: style.css değişti
→ İkisi de alınır, conflict yok

Senaryo 2: Aynı dosyanın farklı bölümleri ✅
main:    style.css satır 1-10 değişti
feature: style.css satır 50-60 değişti
→ İkisi de alınır, conflict yok

Senaryo 3: Aynı dosyanın aynı satırları ❌
main:    style.css satır 5: "color: red;"
feature: style.css satır 5: "color: blue;"
→ CONFLICT! Git karar veremez. Sen çözeceksin.

Merge Sonrası Temizlik

Merge başarılı olduğunda, eski branch'i silmek iyi pratiktir:

# Merge et
$ git switch main
$ git merge feature/login

# Branch'i sil (merge edildiğinden -d yeterli)
$ git branch -d feature/login
Deleted branch feature/login (was abc1234).

Merge'ü İptal Etme

Merge sırasında (özellikle conflict çıktıysa) vazgeçmek istersen:

# Merge'ü iptal et, önceki duruma dön
$ git merge --abort

# Merge'den sonra (commit'lendiyse) geri al
$ git revert -m 1 HEAD
# -m 1: Birinci parent'ı (main'i) koru

Pratik Senaryo: Tam Bir Feature Workflow

# === 1. PROJE ===
$ mkdir team-project && cd team-project && git init
$ cat > index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Ekip Projesi</title></head>
<body>
    <h1>Ana Sayfa</h1>
    <nav>
        <a href="/">Ana Sayfa</a>
    </nav>
</body>
</html>
EOF
$ git add . && git commit -m "feat: Proje başlatıldı"

# === 2. FEATURE BRANCH ===
$ git switch -c feature/contact-page

# İletişim sayfası oluştur
$ cat > contact.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>İletişim</title></head>
<body>
    <h1>İletişim</h1>
    <form>
        <input type="text" placeholder="Adınız">
        <input type="email" placeholder="E-posta">
        <button>Gönder</button>
    </form>
</body>
</html>
EOF
$ git add . && git commit -m "feat: İletişim sayfası oluşturuldu"

# Navigasyona link ekle
$ sed -i 's|</nav>|    <a href="/contact">İletişim</a>\n    </nav>|' index.html
$ git add . && git commit -m "feat: Navigasyona iletişim linki eklendi"

# === 3. BU ARADA MAIN'DE ÇALIŞMA ===
$ git switch main

# Header stili eklendi (başka bir geliştirici tarafından)
$ cat > style.css << 'EOF'
body { font-family: sans-serif; margin: 0; }
nav { background: #333; padding: 10px; }
nav a { color: white; margin-right: 10px; }
EOF
$ git add . && git commit -m "feat: Temel stiller eklendi"

# === 4. MERGE ===
$ git log --oneline --graph --all
* abc1234 (HEAD -> main) feat: Temel stiller eklendi
| * def5678 (feature/contact-page) feat: Navigasyona iletişim linki eklendi
| * ghi9012 feat: İletişim sayfası oluşturuldu
|/
* jkl3456 feat: Proje başlatıldı

# İki branch'te de commit var → 3-way merge olacak
$ git merge feature/contact-page
# Editör açılır: "Merge branch 'feature/contact-page'"
# Kaydet ve kapat

$ git log --oneline --graph
*   mno7890 (HEAD -> main) Merge branch 'feature/contact-page'
|\
| * def5678 (feature/contact-page) feat: Navigasyona iletişim linki eklendi
| * ghi9012 feat: İletişim sayfası oluşturuldu
* | abc1234 feat: Temel stiller eklendi
|/
* jkl3456 feat: Proje başlatıldı

# === 5. TEMİZLİK ===
$ git branch -d feature/contact-page
Deleted branch feature/contact-page (was def5678).

# Dosyalar — her iki branch'in değişiklikleri birleşti
$ ls
contact.html  index.html  style.css

⚠️ Dikkat: git merge komutunu çalıştırmadan önce: 1. Doğru branch'te olduğundan emin ol (git branch veya git status) 2. Working directory temiz olsun (git status → "clean") 3. main'i merge ediyorsan, önce git pull ile güncel olduğundan emin ol


Octopus Merge — Birden Fazla Branch'i Birleştirme

Nadir de olsa, aynı anda birden fazla branch'i merge etmek gerekebilir:

# 3 feature branch'i aynı anda merge et
$ git merge feature/login feature/payment feature/search

# Git "octopus" stratejisini kullanır
# Conflict yoksa sorunsuz çalışır

Bu, release branch hazırlarken birden fazla feature'ı tek seferde birleştirmek için kullanılabilir. Ama conflict çıkarsa karmaşıklaşır — genelde tek tek merge etmek daha güvenlidir.


Squash Merge — Temiz Geçmiş

Squash merge, feature branch'teki tüm commit'leri tek bir commit olarak main'e ekler:

$ git merge --squash feature/login
Squashing commit abc1234..def5678
Updating abc1234..def5678
Fast-forward (no commit created; use "git commit" to complete)

$ git commit -m "feat: Login sayfası eklendi"
Squash Merge:

feature:  [D] ← [E] ← [F]  (3 commit)
               (WIP)  (fix)  (done)

main (merge --squash sonrası):
[A] ← [B] ← [C] ← [DEF]   (tek commit)
                    "feat: Login sayfası"

Feature'daki 3 ayrı commit tek commit olarak sıkıştırıldı.
WIP ve ara fix commit'leri geçmişi kirletmedi.

Ne zaman kullanılır?

  • Feature branch'inde çok sayıda küçük, WIP commit var

  • Ana geçmişin temiz ve okunabilir olmasını istiyorsun

  • GitHub'da "Squash and merge" butonu bunu yapar

Normal merge:  [A]─[B]─[C]─[M]  (merge commit + tüm feature commit'leri)
                         \  /
                    [D]─[E]─[F]  (feature commit'leri görünür)

Squash merge:  [A]─[B]─[C]─[DEF]  (tek commit, temiz geçmiş)

💡 İpucu: GitHub PR'da üç merge seçeneği sunar: "Create a merge commit" (--no-ff), "Squash and merge" (--squash), "Rebase and merge" (rebase). Ekip olarak hangisini kullanacağınıza karar verin ve tutarlı olun.


Merge ile İlgili Yaygın Hatalar

1. Yanlış Branch'te Merge Yapmak

# ❌ feature'da olup main'i merge etmek istiyordun ama...
$ git branch
* feature/login
  main

$ git merge feature/payment
# Oops! feature/payment'ı feature/login'e merge ettin!

# Geri al:
$ git reset --hard HEAD~1

# ✅ Her zaman merge öncesi branch'ini kontrol et
$ git branch  # Yıldız nerede?
$ git merge feature/payment

2. Merge Öncesi Commit'lenmemiş Değişiklik

# ❌ Working directory kirli
$ git status
# Changes not staged for commit: modified: app.js

$ git merge feature/login
# Merge başarılı olsa bile commit'lenmemiş
# değişiklikler karışabilir

# ✅ Önce temiz ol
$ git stash
$ git merge feature/login
$ git stash pop

3. --no-ff'yi Unutmak

# ❌ Fast-forward — branch geçmişi kaybolur
$ git merge feature/login
# Updating abc1234..def5678
# Fast-forward

# ✅ --no-ff ile branch geçmişini koru
$ git merge --no-ff feature/login
# Merge made by the 'ort' strategy.

# Global ayar:
$ git config --global merge.ff false
# Artık her merge --no-ff gibi davranır

4. Merge Commit Mesajını Boş Bırakmak

# ❌ Varsayılan mesajı kabul etmek
"Merge branch 'feature/login'"
# Ne merge edildiği belli ama NEDEN belli değil

# ✅ Anlamlı merge mesajı
"Merge feature/login: JWT auth ve login formu eklendi

Bu merge ile kullanıcılar:
- Email/şifre ile giriş yapabilir
- 24 saat geçerli JWT token alır
- Remember me seçeneğini kullanabilir

PR #42, Fixes #38"

Merge Stratejisi Seçimi Rehberi

Durum                                    │ Önerilen Strateji
─────────────────────────────────────────┼──────────────────────
Feature branch → main                   │ --no-ff (branch görünsün)
Hotfix → main (acil)                    │ fast-forward (hız)
main → feature (sync)                   │ merge veya rebase
PR merge (GitHub)                       │ Squash and merge
Release branch → main                   │ --no-ff + tag
Büyük merge, çok conflict               │ --no-ff + detaylı mesaj

git log --first-parent: Sadece Merge'leri Gör

Merge commit'li bir geçmişte sadece ana akışı görmek istersen:

$ git log --oneline --first-parent
mno7890 (HEAD -> main) Merge branch 'feature/contact-page'
abc1234 feat: Temel stiller eklendi
jkl3456 feat: Proje başlatıldı

# Feature branch'in iç commit'leri gözükmüyor.
# Sadece merge noktaları ve main üzerindeki commit'ler.

Bu, proje yöneticileri ve büyük projelerde genel akışı görmek için çok faydalı.


Özet

  • Fast-forward merge: Branch'ler ayrışmamışsa, pointer ileri taşınır — yeni commit oluşmaz, düz geçmiş

  • 3-way merge: İki branch'te de yeni commit'ler varsa, ortak atadan yola çıkarak birleştirme yapılır ve merge commit oluşturulur

  • `--no-ff`: Fast-forward mümkün olsa bile merge commit oluşturur — branch geçmişini korur

  • Merge commit'inin iki parent'ı vardır — bu onu sıradan commit'ten ayırır

  • Git, farklı dosya veya aynı dosyanın farklı bölümlerindeki değişiklikleri otomatik birleştirir

  • Aynı dosyanın aynı satırlarında çakışma varsa conflict oluşur — bir sonraki derste çözeceğiz


💡 İpucu: Merge işlemini iyi anlamak, Git'in en temel becerilerinden biridir. Branch açmak kolay — asıl ustalık birleştirmede. Merge'ü teorik olarak bilmekle yetinme; farklı senaryolarda pratik yap: fast-forward, 3-way merge, squash merge, --no-ff. Her birinin çıktısını git log --oneline --graph ile incele ve farkı gözünle gör.

*Bir sonraki derste merge conflict'leri — neden oluşur, nasıl tanınır ve nasıl çözülür — öğreneceğiz. Korkma, göründüğü kadar zor değil!*