← Kursa Dön
📄 Text · 30 min

Merge Conflict Çözme

Giriş: Neden Conflict Oluşur?

Git, dosyaları birleştirmekte harika bir iş çıkarır. Farklı dosyalardaki değişiklikleri, hatta aynı dosyanın farklı bölümlerindeki değişiklikleri otomatik birleştirir. Ama bir durum var ki Git "Ben karar veremiyorum, sen çöz" der: aynı dosyanın aynı satırlarında iki farklı değişiklik yapılmışsa.

Merge conflict, yeni başlayanların en çok korktuğu durum. Ekranda garip semboller belirir, terminalde kırmızı uyarılar çıkar ve panik başlar. Ama merak etme — conflict çözmek bir beceridir ve bu derste ustalaşacağız.


🎬 Analoji: İki Şef, Bir Tarif

Bir restoran düşün. İki şef aynı tarifi farklı şekillerde güncelliyor:

  • Şef A: "Tuz 1 çay kaşığı olsun" → "Tuz 1.5 çay kaşığı olsun"

  • Şef B: "Tuz 1 çay kaşığı olsun" → "Tuz yarım çay kaşığı olsun"

İkisi de aynı satırı değiştirmiş ama farklı yönlerde. Tarifi birleştirmeye çalışan kişi (Git) burada duruyor ve soruyor: "Hangi miktarı kullanayım? Ben bilemem, siz karar verin."

İşte conflict bu. Git hakem değil — çözmesi gereken sensin.


Conflict Nasıl Oluşur?

Adım Adım Conflict Oluşturalım

# Proje oluştur
$ mkdir conflict-lab && cd conflict-lab && git init

# Ortak başlangıç dosyası
$ cat > menu.txt << 'EOF'
=== RESTORAN MENÜSÜ ===

BAŞLANGIÇLAR:
- Çorba: 30 TL
- Salata: 25 TL

ANA YEMEKLER:
- Izgara: 80 TL
- Makarna: 60 TL

TATLILAR:
- Künefe: 45 TL
- Baklava: 40 TL
EOF

$ git add . && git commit -m "feat: Menü oluşturuldu"

Şimdi iki branch oluşturalım ve aynı satırları değiştirelim:

# === BRANCH 1: Fiyat güncellemesi ===
$ git switch -c feature/price-update

# Çorba fiyatını 35'e çıkar
$ sed -i 's/Çorba: 30 TL/Çorba: 35 TL/' menu.txt
# Izgara fiyatını 90'a çıkar
$ sed -i 's/Izgara: 80 TL/Izgara: 90 TL/' menu.txt

$ git add . && git commit -m "feat: Fiyatlar güncellendi (çorba 35, ızgara 90)"

# === BRANCH 2: Menü yenileme ===
$ git switch main
$ git switch -c feature/menu-refresh

# Çorba fiyatını 28'e indir (kampanya!)
$ sed -i 's/Çorba: 30 TL/Çorba: 28 TL (KAMPANYA!)/' menu.txt
# Izgara yerine kebap koy
$ sed -i 's/Izgara: 80 TL/Kebap: 95 TL/' menu.txt

$ git add . && git commit -m "feat: Menü yenilendi (kampanya + kebap)"

Şimdi duruma bakalım:

$ git log --oneline --graph --all
* def5678 (feature/menu-refresh) feat: Menü yenilendi
| * abc1234 (feature/price-update) feat: Fiyatlar güncellendi
|/
* 1a2b3c4 (main) feat: Menü oluşturuldu

İki branch, aynı dosyanın aynı satırlarını farklı şekillerde değiştirmiş. Merge edelim:

$ git switch main
$ git merge feature/price-update
# Bu sorunsuz olur (fast-forward)

$ git merge feature/menu-refresh
Auto-merging menu.txt
CONFLICT (content): Merge conflict in menu.txt
Automatic merge failed; fix conflicts and then commit the result.

CONFLICT! Git otomatik birleştiremedi.


Conflict Marker'ları: Garip Semboller

Conflict'li dosyayı açalım:

$ cat menu.txt
=== RESTORAN MENÜSÜ ===

BAŞLANGIÇLAR:
<<<<<<< HEAD
- Çorba: 35 TL
=======
- Çorba: 28 TL (KAMPANYA!)
>>>>>>> feature/menu-refresh
- Salata: 25 TL

ANA YEMEKLER:
<<<<<<< HEAD
- Izgara: 90 TL
=======
- Kebap: 95 TL
>>>>>>> feature/menu-refresh
- Makarna: 60 TL

TATLILAR:
- Künefe: 45 TL
- Baklava: 40 TL

Bu marker'ları okuyalım:

<<<<<<< HEAD
Senin branch'indeki (main) değişiklik
=======
Gelen branch'teki (feature/menu-refresh) değişiklik
>>>>>>> feature/menu-refresh
┌──────────────────────────────────────────────────┐
│  <<<<<<< HEAD                                    │
│  ┌──────────────────────────────────────────┐    │
│  │ Senin versiyonun (current - mevcut)       │    │
│  │ Şu anda main'de olan hali                │    │
│  └──────────────────────────────────────────┘    │
│  =======                                        │
│  ┌──────────────────────────────────────────┐    │
│  │ Gelen versiyonu (incoming - gelen)        │    │
│  │ Merge etmeye çalıştığın branch'teki      │    │
│  └──────────────────────────────────────────┘    │
│  >>>>>>> feature/menu-refresh                    │
└──────────────────────────────────────────────────┘

Conflict Çözme: 4 Yaklaşım

Yaklaşım 1: Manuel Çözüm (En Temel)

Dosyayı aç, marker'ları sil, doğru içeriği bırak:

$ nano menu.txt
# veya herhangi bir editör

Conflict'li kısımları düzenle. Seçeneklerin:

A) Senin versiyonunu tut ("ours"):

- Çorba: 35 TL

B) Gelen versiyonu tut ("theirs"):

- Çorba: 28 TL (KAMPANYA!)

C) İkisini birleştir (en yaygın):

- Çorba: 35 TL (KAMPANYA: 28 TL)

D) Tamamen yeni bir şey yaz:

- Çorba: 32 TL

Diyelim ki ikisini birleştirmek istiyoruz:

$ cat > menu.txt << 'EOF'
=== RESTORAN MENÜSÜ ===

BAŞLANGIÇLAR:
- Çorba: 35 TL (KAMPANYA: 28 TL)
- Salata: 25 TL

ANA YEMEKLER:
- Kebap: 95 TL
- Makarna: 60 TL

TATLILAR:
- Künefe: 45 TL
- Baklava: 40 TL
EOF

⚠️ Dikkat: <<<<<<<, =======, >>>>>>> marker'larını kesinlikle silmelisin. Bunlar dosyada kalırsa, kodun bozulur. Conflict çözdükten sonra dosyada bu marker'ları ara (grep -n "<<<" dosya.txt).

Şimdi çözümü kaydet:

# Çözülen dosyayı staging'e al
$ git add menu.txt

# Merge commit'i oluştur
$ git commit -m "merge: Fiyat güncellemesi ve menü yenilemesi birleştirildi"

# veya mesajsız — Git varsayılan merge mesajını kullanır
$ git commit
# Editör açılır: "Merge branch 'feature/menu-refresh'"
$ git log --oneline --graph
*   mno7890 (HEAD -> main) merge: Fiyat + menü birleştirildi
|\
| * def5678 (feature/menu-refresh) feat: Menü yenilendi
* | abc1234 (feature/price-update) feat: Fiyatlar güncellendi
|/
* 1a2b3c4 feat: Menü oluşturuldu

Yaklaşım 2: VS Code ile Çözme (Önerilen)

VS Code, conflict'leri çözmek için mükemmel bir arayüz sunar:

VS Code Conflict Görünümü:

┌──────────────────────────────────────────────────┐
│  Accept Current Change | Accept Incoming Change  │
│  Accept Both Changes  | Compare Changes          │
│──────────────────────────────────────────────────│
│                                                  │
│  <<<<<<< HEAD (Current Change)                   │
│  ┃ - Çorba: 35 TL                               │  ← Yeşil arka plan
│  =======                                        │
│  ┃ - Çorba: 28 TL (KAMPANYA!)                   │  ← Mavi arka plan
│  >>>>>>> feature/menu-refresh (Incoming Change)  │
│                                                  │
└──────────────────────────────────────────────────┘

VS Code'da tıkla:

  • Accept Current Change: Senin versiyonu tut

  • Accept Incoming Change: Gelen versiyonu tut

  • Accept Both Changes: İkisini de koy (alt alta)

  • Compare Changes: Farkları yan yana gör

Yaklaşım 3: Git Mergetool

Git, harici merge araçları ile entegre olabilir:

# Varsayılan merge tool'u ayarla
$ git config --global merge.tool vscode
$ git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'

# Conflict olduğunda
$ git mergetool
# VS Code 4-panelli merge editörü açılır

Yaklaşım 4: Toptan Kabul Etme

Bazen bir tarafı tamamen kabul etmek istersin:

# Tüm conflict'lerde senin versiyonunu tut
$ git checkout --ours menu.txt

# Tüm conflict'lerde gelen versiyonu tut
$ git checkout --theirs menu.txt

# Sonra staging'e al ve commit'le
$ git add menu.txt
$ git commit

💡 İpucu: --ours ve --theirs kullanırken dikkatli ol. Merge ve rebase'de yönleri ters olabilir! Merge'de --ours = mevcut branch, --theirs = gelen branch. Rebase'de tam tersi.


Conflict Durumunu İnceleme

Conflict sırasında Git'in durumunu anlamak:

$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   menu.txt

no changes added to commit (use "git add" and/or "git commit -a")

"Unmerged paths" = çözülmemiş conflict'ler.

# Conflict'li dosyaları listele
$ git diff --name-only --diff-filter=U
menu.txt

# Conflict detaylarını gör
$ git diff
# <<< === >>> marker'lı diff çıktısı

Merge'ü İptal Etme

Conflict çözmek istemiyorsan veya yanlış merge yaptıysan:

# Merge'ü iptal et — her şeyi merge öncesine geri al
$ git merge --abort

Bu komut, merge işlemini tamamen geri alır ve seni merge öncesi duruma döndürür. Hiçbir şey kaybolmaz.


Conflict Önleme Stratejileri

Conflict'i çözmek güzel ama önlemek daha güzel:

1. Sık Merge / Pull Et

# Feature branch'inde çalışırken, main'i sık sık çek
$ git switch feature/my-work
$ git merge main
# main'deki değişiklikleri feature'a al
# Küçük conflict'leri erken çöz

2. Kısa Ömürlü Branch'ler

Branch ne kadar uzun yaşarsa, conflict riski o kadar artar. 1-2 gün içinde merge et.

3. Ekip İletişimi

"Ben bu dosyada çalışıyorum" demek, conflict'i önlemenin en basit yolu. Ekip içi iletişim, teknolojinin yerine geçmez.

4. Dosya Yapısını Modüler Tut

Büyük dosyalar = çok conflict. Küçük, modüler dosyalar = az conflict. Bir dosya 500+ satırsa, bölmeyi düşün.

5. .gitattributes ile Merge Stratejisi Belirle

# .gitattributes dosyası
# Bu dosyalar conflict olursa otomatik bizim versiyonu al
database/schema.rb merge=ours
package-lock.json merge=ours

Pratik Senaryo: Gerçekçi Conflict Çözümü

# İki geliştirici aynı fonksiyonu değiştiriyor

# === Proje Kurulumu ===
$ mkdir real-conflict && cd real-conflict && git init

$ cat > auth.js << 'EOF'
function login(username, password) {
    if (!username || !password) {
        return { error: "Eksik bilgi" };
    }
    
    const user = findUser(username);
    if (user && user.password === password) {
        return { success: true, token: generateToken() };
    }
    
    return { error: "Hatalı giriş" };
}
EOF

$ git add . && git commit -m "feat: Login fonksiyonu"

# === GELİŞTİRİCİ A: Güvenlik iyileştirmesi ===
$ git switch -c feature/security

$ cat > auth.js << 'EOF'
function login(username, password) {
    if (!username || !password) {
        return { error: "Kullanıcı adı ve şifre zorunludur" };
    }
    
    const user = findUser(username);
    if (user && bcrypt.compare(password, user.hashedPassword)) {
        const token = generateToken(user.id, '24h');
        logActivity(user.id, 'login');
        return { success: true, token };
    }
    
    logActivity(null, 'failed_login', { username });
    return { error: "Geçersiz kullanıcı adı veya şifre" };
}
EOF

$ git add . && git commit -m "feat: bcrypt + activity log eklendi"

# === GELİŞTİRİCİ B: Rate limiting ===
$ git switch main
$ git switch -c feature/rate-limit

$ cat > auth.js << 'EOF'
function login(username, password) {
    if (isRateLimited(username)) {
        return { error: "Çok fazla deneme. 5 dakika bekleyin." };
    }
    
    if (!username || !password) {
        return { error: "Eksik bilgi" };
    }
    
    const user = findUser(username);
    if (user && user.password === password) {
        resetRateLimit(username);
        return { success: true, token: generateToken() };
    }
    
    incrementRateLimit(username);
    return { error: "Hatalı giriş" };
}
EOF

$ git add . && git commit -m "feat: Rate limiting eklendi"

# === MERGE ===
$ git switch main
$ git merge feature/security
# Fast-forward, sorunsuz

$ git merge feature/rate-limit
# CONFLICT!

# === ÇÖZÜM: İki özelliği birleştir ===
$ cat > auth.js << 'EOF'
function login(username, password) {
    if (isRateLimited(username)) {
        return { error: "Çok fazla deneme. 5 dakika bekleyin." };
    }
    
    if (!username || !password) {
        return { error: "Kullanıcı adı ve şifre zorunludur" };
    }
    
    const user = findUser(username);
    if (user && bcrypt.compare(password, user.hashedPassword)) {
        resetRateLimit(username);
        const token = generateToken(user.id, '24h');
        logActivity(user.id, 'login');
        return { success: true, token };
    }
    
    incrementRateLimit(username);
    logActivity(null, 'failed_login', { username });
    return { error: "Geçersiz kullanıcı adı veya şifre" };
}
EOF

$ git add auth.js
$ git commit -m "merge: Security + rate limiting birleştirildi"

Bu örnekte conflict çözmek sadece marker silmek değil — iki özelliği mantıksal olarak birleştirmek. Rate limiting kontrolü önce, bcrypt hash karşılaştırması ve activity logging içeride.


Conflict Çözme İpuçları

diff3 Conflict Style

Varsayılan conflict marker'ları sadece "senin" ve "gelen" versiyonu gösterir. Ama ortak ata (base) da çok değerli bilgi verir:

# diff3 style'ı etkinleştir
$ git config --global merge.conflictstyle diff3

Artık conflict marker'ları 3 bölüm gösterir:

<<<<<<< HEAD
- Çorba: 35 TL
||||||| common ancestor
- Çorba: 30 TL
=======
- Çorba: 28 TL (KAMPANYA!)
>>>>>>> feature/menu-refresh

Ortak ata (30 TL) görünce daha bilinçli karar verirsin. "İkisi de fiyatı değiştirmiş — biri artırmış, biri indirmiş" hemen anlaşılıyor.

rerere — Aynı Conflict'i Bir Daha Çözme

rerere (Reuse Recorded Resolution), daha önce çözdüğün conflict'leri hatırlar ve tekrar çıktığında otomatik çözer:

# rerere'yi etkinleştir
$ git config --global rerere.enabled true

# İlk seferde conflict çözersin → Git çözümü kaydeder
# İkinci seferde aynı conflict çıkarsa → Git otomatik çözer!

# rerere cache'ini göster
$ git rerere status
# src/auth.js

# Son çözümü unut (yanlış çözdüysen)
$ git rerere forget src/auth.js

Bu özellikle rebase workflow'unda çok faydalıdır — aynı branch'i tekrar tekrar rebase ederken aynı conflict'ler çıkabilir.


Conflict Simülasyonu: Pratik Yapın

Conflict çözmede ustalaşmanın en iyi yolu pratik. İşte hızlı bir simülasyon:

# Hızlı conflict simülasyonu (3 dakika)
$ mkdir conflict-practice && cd conflict-practice && git init

# Ortak başlangıç
$ echo "version = 1" > config.txt
$ git add . && git commit -m "initial"

# Branch A
$ git checkout -b branch-a
$ echo "version = 2-alpha" > config.txt
$ git add . && git commit -m "version 2-alpha"

# Branch B
$ git checkout main
$ git checkout -b branch-b
$ echo "version = 2-beta" > config.txt
$ git add . && git commit -m "version 2-beta"

# Merge → CONFLICT!
$ git checkout main
$ git merge branch-a  # Fast-forward, sorunsuz
$ git merge branch-b  # CONFLICT!

# Çöz ve pratik yap
$ cat config.txt  # Marker'ları gör
$ echo "version = 2" > config.txt  # Kararını yaz
$ git add config.txt
$ git commit -m "merge: resolve version conflict"

# Temizle ve tekrarla!
$ cd .. && rm -rf conflict-practice

Bu simülasyonu farklı senaryolarla (farklı dosyalar, çok satırlı değişiklikler, üç yollu merge) tekrarlayarak conflict çözme kasınızı geliştirin.


Conflict Çözme Kontrol Listesi

☐ git status ile conflict'li dosyaları bul
☐ Her dosyayı aç ve <<<, ===, >>> marker'larını bul
☐ Her conflict için karar ver: ours, theirs, veya birleştir
☐ Marker'ları temizle (hiç kalmamalı!)
☐ Dosyayı test et (syntax error yok mu?)
☐ git add ile çözülen dosyaları stage'le
☐ git commit ile merge'ü tamamla
☐ git log --graph ile sonucu doğrula

Özet

  • Conflict, aynı dosyanın aynı satırlarında iki farklı değişiklik olduğunda oluşur — Git hangisini seçeceğini bilemez

  • Conflict marker'ları: <<<<<<< HEAD (senin), ======= (ayırıcı), >>>>>>> branch (gelen) — hepsini silmelisin

  • Çözüm seçenekleri: kendi versiyonunu tut, gelen versiyonu tut, ikisini birleştir, veya tamamen yeni yaz

  • git merge --abort ile merge'ü iptal edip önceki duruma dönebilirsin

  • Conflict'i önlemenin en iyi yolları: sık merge, kısa branch'ler, ekip iletişimi ve modüler dosya yapısı

  • VS Code ve git mergetool gibi araçlar conflict çözmeyi kolaylaştırır


💡 İpucu: Conflict korkutucu görünür ama aslında Git'in sana "Burada insan kararı gerekiyor" demesidir. Bu bir hata değil, bir fırsattır — iki geliştiricinin çalışmasını bilinçli olarak birleştirme fırsatı. Ne kadar çok conflict çözersen, o kadar rahat edersin. Pratik, pratiğin anahtarı pratik!

*Bir sonraki derste branch stratejilerini — Git Flow, GitHub Flow, Trunk-Based Development — öğreneceğiz. Hangi strateji hangi ekip için uygun?*