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 commitFast-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 eder2. 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"):
Common ancestor (ortak ata): C — iki branch'in ayrıldığı nokta
main'in ucu: F
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 commitMerge 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-ffkullanmayı 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-parentile 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ırOrt (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ırOurs
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'leOtomatik 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) koruPratik 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 mergekomutunu çalıştırmadan önce: 1. Doğru branch'te olduğundan emin ol (git branchveyagit status) 2. Working directory temiz olsun (git status→ "clean") 3.main'i merge ediyorsan, öncegit pullile 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ışırBu, 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/payment2. 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 pop3. --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ır4. 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ı mesajgit 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 --graphile 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!*
AI Asistan
Sorularını yanıtlamaya hazır