← Kursa Dön
📄 Text · 30 min

İleri Conflict Yönetimi

Giriş — Trafik Kazası Analojisi

Merge conflict'leri trafik kazalarına benzer. İki araç aynı kavşaktan aynı anda geçmeye çalışırsa çarpışma kaçınılmazdır. Peki trafik kazalarını nasıl önlersiniz? Trafik ışıkları, şerit çizgileri, kavşak düzenlemeleri ve sürücü eğitimi ile. Kaza olduktan sonra da polis raporu, sigorta, tamir — bunların hepsi gereklidir ama en iyisi kazanın hiç olmamasıdır.

Git'te de aynı prensip geçerlidir. Önceki derslerde basit conflict çözümünü öğrendiniz. Bu derste bir adım ileri gidiyoruz:

  • Merge stratejileri ile Git'e nasıl çözüm yapacağını söylemek

  • rerere ile aynı conflict'i bir daha çözmemek

  • Conflict önleme ile sorunun kök nedenini ortadan kaldırmak

Merge Stratejileri — Git'e Talimat Vermek

Git Merge Nasıl Çalışır? (Hatırlatma)

Git merge yaparken üç noktayı karşılaştırır:

         Base (Ortak Ata)
            /        \
           /          \
      Ours (HEAD)   Theirs (merge edilen)
  • Base: İki branch'in ayrıldığı nokta

  • Ours: Şu an üzerinde olduğumuz branch (HEAD)

  • Theirs: Merge etmeye çalıştığımız branch

Eğer aynı dosyanın aynı bölümü her iki branch'te de değişmişse, Git otomatik karar veremez ve conflict oluşur. Ama biz Git'e "böyle durumlarda şunu yap" diye talimat verebiliriz.

Recursive Strategy (Varsayılan)

Git'in varsayılan merge stratejisi recursivedir (Git 2.34+ sonrası ort — "Ostensibly Recursive's Twin"):

# Varsayılan — açıkça belirtmeye gerek yok
git merge feature-branch

# Açıkça belirtmek isterseniz
git merge -s recursive feature-branch
# veya yeni Git'lerde
git merge -s ort feature-branch

Recursive strateji, ortak atayı bulur ve üç yollu merge yapar. Çoğu durumda bu yeterlidir.

Ours Strategy — "Benim Versiyon Kazansın"

ours stratejisi, merge commit'i oluşturur ama karşı tarafın hiçbir değişikliğini almaz. Tüm conflict'ler "benim versiyonum" ile çözülür.

# Ours strategy — theirs'deki tüm değişiklikleri YOKSAY
git merge -s ours feature-branch

⚠️ Dikkat: -s ours (strategy) ile -X ours (strategy option) farklı şeylerdir! -s ours karşı tarafın TÜM değişikliklerini yoksayar. -X ours sadece conflict olan kısımlarda kendi versiyonumuzu tercih eder, conflict olmayan değişiklikleri normal şekilde alır.

# ❌ -s ours: Karşı tarafın HİÇBİR değişikliğini alma
git merge -s ours feature-branch

# ✅ -X ours: Sadece conflict'lerde kendi versiyonunu tercih et
git merge -X ours feature-branch

Ne zaman kullanılır?

  • Bir branch'i "merge edilmiş" olarak işaretlemek ama değişikliklerini almamak istediğinizde

  • Eski, artık gerekli olmayan bir branch'i geçmişe entegre ederken

  • "Bu branch'in işini zaten farklı şekilde yaptık" durumlarında

Theirs Strategy Option — "Onların Versiyonu Kazansın"

Git'te -s theirs diye bir strateji yoktur, ama -X theirs (strategy option) vardır:

# Conflict'lerde karşı tarafın versiyonunu tercih et
git merge -X theirs feature-branch
# Örnek senaryo:
# main'de:    color = "blue"
# feature'da: color = "red"
# Conflict oluşur

# -X theirs ile: color = "red" (feature kazanır)
# -X ours ile:   color = "blue" (main kazanır)

Ne zaman kullanılır?

  • Diğer branch'in değişikliklerinin daha güncel/doğru olduğunu bildiğinizde

  • Upstream'den gelen değişiklikleri almak istediğinizde

  • "Onlar daha çok çalıştı, onların versiyonu geçerli" durumlarında

Rebase'de Ours/Theirs Ters Çalışır!

Bu, Git'in en kafa karıştırıcı detaylarından biridir:

# MERGE'de:
# ours  = şu anki branch (HEAD)
# theirs = merge edilen branch

# REBASE'de:
# ours  = rebase EDİLEN branch (üzerine rebase yaptığımız)
# theirs = bizim branch (rebase YAPAN)

Neden böyle? Çünkü rebase aslında commit'lerinizi teker teker "karşı branch üzerine" yeniden uygular. Her bir adımda, "üzerine uyguladığınız branch" geçici olarak HEAD (yani ours) olur:

# Senaryo: feature branch'i main üzerine rebase ediyoruz
git checkout feature
git rebase main

# Rebase sırasında conflict olursa:
# ours  = main (üzerine rebase ettiğimiz)  ← KAFAKARIŞTİRICI!
# theirs = feature (bizim commit'imiz)

# Conflict'te kendi değişikliğimizi korumak için:
git rebase -X theirs main   # ← rebase'de THEIRS biziz!
┌──────────────────────────────────────────────┐
│          MERGE vs REBASE: Ours/Theirs        │
├──────────────────────────────────────────────┤
│                                              │
│  git merge -X ours feature                   │
│  → Conflict'te main (HEAD) kazanır           │
│                                              │
│  git merge -X theirs feature                 │
│  → Conflict'te feature kazanır               │
│                                              │
│  git rebase -X ours main                     │
│  → Conflict'te main kazanır (rebase hedefi)  │
│                                              │
│  git rebase -X theirs main                   │
│  → Conflict'te feature kazanır (bizim)       │
│                                              │
└──────────────────────────────────────────────┘

💡 İpucu: Bu karışıklığı aşmanın en iyi yolu: rebase'de "benim değişikliklerimi koru" demek istiyorsanız -X theirs kullanın. Evet, counter-intuitive ama Git böyle çalışır.

Checkout ile Tek Dosya Conflict Çözme

Bazen tüm merge'i bir stratejiyle çözmek yerine, dosya dosya karar vermek istersiniz:

# Merge başlat (conflict oluşur)
git merge feature-branch

# Conflict olan dosyayı listele
git status
# both modified: src/config.js
# both modified: src/utils.js

# config.js'de kendi versiyonumuzu al
git checkout --ours src/config.js

# utils.js'de onların versiyonunu al
git checkout --theirs src/utils.js

# Stage et ve merge'i tamamla
git add src/config.js src/utils.js
git commit

Bu, hibrit bir yaklaşımdır: bazı dosyalarda siz kazanırsınız, bazılarında karşı taraf.

git rerere — Aynı Conflict'i İki Kez Çözme

Ne Anlama Geliyor?

rerere = "Reuse Recorded Resolution" (Kaydedilmiş Çözümü Yeniden Kullan)

Diyelim ki bir branch'i rebase ederken bir conflict çözdünüz. Sonra rebase'i geri aldınız ve tekrar denediğinizde aynı conflict çıktı. Aynı çözümü tekrar mı yapacaksınız? İşte rerere bunu otomatize eder.

Bunu, bir GPS'in "daha önce bu yolda trafik vardı ve şu alternatif rotayı kullanmıştınız" diye hatırlamasına benzetebilirsiniz.

rerere Nasıl Çalışır?

1. Conflict oluşur
2. Siz çözersiniz
3. rerere çözümü kaydeder (fingerprint + resolution)
4. Aynı conflict tekrar oluşursa
5. rerere otomatik olarak aynı çözümü uygular

Aktifleştirme

# Global olarak aç
git config --global rerere.enabled true

# Sadece bu repo için aç
git config rerere.enabled true

Pratik Örnek

# Senaryo: Long-running feature branch, sık rebase

# 1. İlk rebase denemesi
git checkout feature/big-refactor
git rebase main

# Conflict çıktı!
# CONFLICT (content): Merge conflict in src/api/handler.js

# 2. Conflict'i çöz
# ... düzelt ...
git add src/api/handler.js
git rebase --continue

# rerere çözümü kaydetti:
# Recorded preimage for 'src/api/handler.js'
# Recorded resolution for 'src/api/handler.js'

# 3. Bir şey yanlış gitti, rebase'i geri al
git rebase --abort

# 4. Tekrar dene
git rebase main

# Aynı conflict — ama bu sefer:
# Resolved 'src/api/handler.js' using previous resolution.
# Otomatik çözüldü! 🎉

rerere Veritabanını Yönetme

# Kaydedilmiş çözümleri göster
git rerere status

# Kalan çözümleri göster (conflict durumunda)
git rerere remaining

# Çözüm detayını göster
git rerere diff

# Belirli bir çözümü sıfırla (yanlış çözdüyseniz)
git rerere forget src/api/handler.js

# Tüm rerere cache'ini temizle
rm -rf .git/rr-cache/

rerere'nin Gerçek Gücü

rerere özellikle şu durumlarda hayat kurtarır:

1. Long-lived feature branch'ler
   → main sık güncellenir, her rebase'de aynı conflict
   
2. Release branch'ler
   → Cherry-pick'lerde tekrarlayan conflict'ler

3. Merge treniyle workflow
   → Birden fazla branch sırayla merge edilirken

4. Rebase-heavy workflow
   → Interactive rebase, squash sonrası tekrar conflict

⚠️ Dikkat: rerere bir conflict'i yanlış çözerseniz, aynı yanlış çözümü sonraki seferlerde de otomatik uygular. Bu yüzden ilk çözümün doğru olduğundan emin olun. Yanlış çözdüyseniz git rerere forget <dosya> ile silin.

Conflict Önleme Stratejileri

Conflict'i çözmekten daha iyisi, conflict'in hiç oluşmamasıdır. İşte profesyonel ekiplerin uyguladığı önleme stratejileri:

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

Conflict Riski vs Branch Yaşam Süresi:

Risk
  ▲
  │                          ╱
  │                        ╱
  │                      ╱
  │                   ╱
  │                ╱
  │           ╱╱╱
  │        ╱╱
  │     ╱╱
  │  ╱╱
  └────────────────────────────────► Gün
   1   2   3   5   7   10  14  21

Kural basit: branch ne kadar uzun yaşarsa, conflict riski o kadar artar.

# ❌ Kötü — 3 haftalık feature branch
git checkout -b feature/mega-refactor
# ... 3 hafta boyunca çalış, main'den hiç sync olma ...
# Merge zamanı → 50+ conflict 💀

# ✅ İyi — kısa, odaklı branch'ler
git checkout -b feature/add-user-model     # 1-2 gün → merge
git checkout -b feature/add-user-api       # 1-2 gün → merge
git checkout -b feature/add-user-frontend  # 1-2 gün → merge

2. Sık Sync (Rebase/Merge from Main)

# Her gün sabah rutin
git fetch origin
git rebase origin/main

# veya merge tercih ediyorsanız
git merge origin/main

Bunu yapmak küçük, yönetilebilir conflict'lerin erken çözülmesini sağlar. 1 conflict çözmek kolay, 50 conflict çözmek kabus.

3. Dosya Sahipliği (Ownership)

# Ekip içi kural: "Kim hangi dosyadan sorumlu?"

# CODEOWNERS dosyası ile:
# .github/CODEOWNERS
/src/auth/       @ahmet
/src/payment/    @mehmet
/src/ui/         @ayse
/src/api/        @fatma

# Bu sayede:
# - Ahmet auth klasörüne dokunur, Mehmet payment'a
# - Aynı dosyayı iki kişi aynı anda değiştirmez
# - Conflict riski dramatik olarak düşer

4. Feature Flags ile Trunk-Based Development

Büyük feature'ları branch yerine feature flag arkasında geliştirmek, conflict'i tamamen ortadan kaldırır:

// Feature flag ile geliştirme
// Doğrudan main'de commit edilir ama kullanıcı görmez

function Dashboard() {
  if (featureFlags.isEnabled('new-dashboard')) {
    return <NewDashboard />;  // Yeni kod
  }
  return <OldDashboard />;    // Mevcut kod
}
# Branch yok, conflict yok
git checkout main
# ... feature flag arkasında geliştir ...
git commit -m "feat: add new dashboard (behind flag)"
git push origin main

# Flag açıldığında yeni feature aktif olur
# Flag kapatıldığında eski davranış devam eder

5. Modüler Mimari

# ❌ Monolitik yapı — herkes aynı dosyaları değiştirir
src/
├── app.js          (herkes dokunuyor → conflict cenneti)
├── utils.js        (herkes dokunuyor → conflict cenneti)
└── styles.css      (herkes dokunuyor → conflict cenneti)

# ✅ Modüler yapı — herkes kendi modülünde çalışır
src/
├── modules/
│   ├── auth/
│   │   ├── auth.controller.js
│   │   ├── auth.service.js
│   │   └── auth.styles.css
│   ├── cart/
│   │   ├── cart.controller.js
│   │   ├── cart.service.js
│   │   └── cart.styles.css
│   └── payment/
│       ├── payment.controller.js
│       ├── payment.service.js
│       └── payment.styles.css
└── shared/
    ├── utils/        (dikkatli değiştirilmeli)
    └── components/   (dikkatli değiştirilmeli)

6. Lock Dosyaları ve Otomatik Merge

package-lock.json, yarn.lock, pnpm-lock.yaml gibi dosyalar en sık conflict kaynağıdır. Çözüm:

# .gitattributes dosyasına ekleyin:
# Lock dosyalarını merge etme, yeniden oluştur
package-lock.json merge=ours
yarn.lock merge=ours
pnpm-lock.yaml merge=ours
# Conflict olursa pratik çözüm:
git checkout --theirs package-lock.json  # veya --ours
npm install                               # Yeniden oluştur
git add package-lock.json
git commit

💡 İpucu: Lock dosyası conflict'lerinde en güvenli yol: lock dosyasını sil, npm install / pnpm install çalıştır ve yeniden oluştur. Lock dosyasının içini merge etmeye çalışmak genelde daha fazla sorun yaratır.

7. Merge Conflict Linter

CI'da merge conflict marker'ları kalıntı bırakabilir (yanlışlıkla commit edilmiş <<<<<<< satırları). Bunu CI ile tespit edin:

# .github/workflows/ci.yml
- name: Check for conflict markers
  run: |
    if grep -rn '<<<<<<< \|======= \|>>>>>>> ' --include='*.js' --include='*.ts' --include='*.jsx' --include='*.tsx' .; then
      echo "❌ Conflict markers found!"
      exit 1
    fi

İleri Düzey: Merge Driver

Bazı dosya türleri için özel merge davranışı tanımlayabilirsiniz:

# .gitattributes
# Changelog dosyasını otomatik merge et (union: her iki tarafı da al)
CHANGELOG.md merge=union

# Binary dosyaları merge etmeye çalışma
*.png merge=binary
*.jpg merge=binary

# Özel merge driver tanımla
*.json merge=json-merge
# Özel merge driver kaydet
git config merge.json-merge.driver "json-merge-tool %O %A %B"
git config merge.json-merge.name "JSON Merge Tool"

Gerçek Dünya Senaryosu: Release Crunch

Bir fintech şirketinde çalışıyorsunuz. Release günü yaklaşıyor ve 5 feature branch'in hepsi main'e merge edilmeli:

# Durum:
# main ─── A ─── B ─── C
#           \     \     \
#            \     \     └── feature/payment-v2
#             \     └── feature/new-dashboard
#              └── feature/user-settings
# Artı 2 branch daha...

# Strateji: Sıralı merge, her adımda test

# 1. En az riskli branch ile başla
git checkout main
git merge feature/user-settings      # Küçük, izole değişiklik
# → CI çalıştır, testleri geç

# 2. Bir sonraki
git merge feature/new-dashboard       # Orta risk
# → Conflict varsa çöz
# → CI çalıştır, testleri geç

# 3. En riskli sona kalsın
git merge feature/payment-v2          # Büyük değişiklik
# → Conflict çok olabilir, dikkatli çöz
# → CI çalıştır, MUTLAKA manuel test yap

# 4. Release tag
git tag -a v2.5.0 -m "Release v2.5.0"
git push origin main --tags

Conflict Çıktığında Strateji

# Merge başlat
git merge feature/payment-v2

# Conflict'leri gör
git status
# both modified:   src/api/payment.js
# both modified:   src/config/routes.js
# both modified:   package-lock.json

# 1. package-lock.json → Yeniden oluştur
git checkout --theirs package-lock.json
npm install
git add package-lock.json

# 2. routes.js → Birleştir (her iki route'u da ekle)
code src/config/routes.js  # VS Code'da düzenle
git add src/config/routes.js

# 3. payment.js → Feature branch'in versiyonu daha güncel
git checkout --theirs src/api/payment.js
git add src/api/payment.js

# 4. Merge'i tamamla
git commit -m "merge: integrate payment-v2 into main

Resolved conflicts:
- routes.js: merged both route sets
- payment.js: used feature branch version (more complete)
- package-lock.json: regenerated"

Merge Conflict Debugging

Bazen conflict'in neden oluştuğunu anlamak zordur. İşte debugging araçları:

# Conflict'in detayını göster (3-way diff)
git checkout --conflict=diff3 src/api/handler.js

Normal conflict marker'ları:

<<<<<<< HEAD
const timeout = 3000;
=======
const timeout = 5000;
>>>>>>> feature-branch

diff3 formatıyla (ortak atayı da gösterir):

<<<<<<< HEAD
const timeout = 3000;
||||||| base
const timeout = 1000;
=======
const timeout = 5000;
>>>>>>> feature-branch

Bu, orijinal değerin 1000 olduğunu gösterir. Her iki taraf da değiştirmiş, biri 3000, diğeri 5000 yapmış. Bu bilgi kararınızı kolaylaştırır.

# diff3 formatını varsayılan yap
git config --global merge.conflictstyle diff3

# Veya zdiff3 (daha akıllı — Git 2.35+)
git config --global merge.conflictstyle zdiff3

💡 İpucu: zdiff3 (zealous diff3), conflict bölgesinde her iki tarafta da aynı olan kısımları conflict dışına çıkarır. Bu, çözmeniz gereken conflict boyutunu küçültür.

Merge Log ile Geçmişi İnceleme

# Hangi commit'ler conflict'e neden olmuş olabilir?
git log --merge --oneline
# Sadece conflict olan dosyaları değiştiren commit'leri gösterir

# Belirli bir dosyanın geçmişini iki branch'te karşılaştır
git log main..feature-branch -- src/api/handler.js
git log feature-branch..main -- src/api/handler.js

Özet

Bu derste merge conflict yönetimini ileri seviyeye taşıdık:

  • 🎯 Merge stratejileri-X ours ve -X theirs ile conflict'lerde otomatik karar

  • ⚠️ Ours/Theirs karışıklığı — rebase'de ters çalışır, dikkat!

  • 🔄 rerere — aynı conflict'i bir kez çöz, Git hatırlasın

  • 🛡️ Conflict önleme — kısa branch'ler, sık sync, modüler mimari

  • 📁 Dosya sahipliği — CODEOWNERS ile kimin nereye dokunacağını belirle

  • 🔍 diff3/zdiff3 — ortak atayı görerek daha bilinçli conflict çözümü

  • 🚩 Feature flags — branch kullanmadan büyük feature geliştirme

Bir sonraki derste, repository güvenliği ve erişim kontrolünü inceleyeceğiz: branch protection, required reviews, CODEOWNERS ve organization/team yönetimi.