Skip to content

Bölüm 5 / 12

Çatışma çözme

Conflict işaretleri ve çözüm akışı.

14 dakikalık okuma

3. dersteki üç yönlü birleştirme tablosunun son satırı: iki dal aynı bölgeyi farklı biçimde değiştirmişse Git karar veremez, sana bırakır. Bu ders o anın tamamıdır — çatışma nedir ve nedir değildir, işaretler nasıl okunur (ve neden varsayılan görünüm eksiktir), çözüm akışı ve geri kapısı, aynı çatışmayı iki kez çözmemek için rerere, ve asıl kıdem bilgisi: çatışma bir Git sorunu değil, çalışma biçimi sorunudur; küçültmenin yolu daha iyi merge aracı değil, daha sık entegrasyondur. Bir de Git'in hiç göremediği tür var: derlenen ama bozuk merge — 15. hattın testleri burada merge'ün parçası olur.

01Ne öğreneceksin

  • Çatışmanın tanımı ve türleri: içerik · değiştir/sil · ekle/ekle · ikili dosya
  • Anlamsal çatışma: derlenen ama bozuk merge; testlerin merge'deki rolü
  • İşaretlerin anatomisi; diff3/zdiff3 ile ortak atayı görmek — neden varsayılan yetmez
  • Çözüm akışı: aç → düzelt → add → commit; --abort geri kapısı
  • --ours / --theirs: dosya ve strateji düzeyi; rebase'de tersine dönmesi
  • Index'in üç aşaması (stage 1/2/3) — çatışmanın Git'teki gerçek temsili
  • mergetool ve rerere: kaydedilmiş çözümü yeniden kullanmak
  • Çatışmayı küçültme stratejisi: küçük PR, sık entegrasyon, üretilen dosyalar, kilit dosyaları

02Çatışma nedir, ne değildir

Çatışma, üç yönlü birleştirmenin karar veremediği bölgedir: ortak ataya göre iki taraf da aynı bölgeyi değiştirmiş ve değişiklikler aynı değil. Tanımdaki her kelime önemli: aynı bölge (farklı satırlar çatışmaz, dosyanın iki ucundaki değişiklik sessizce birleşir) ve aynı değil (iki taraf birebir aynı değişikliği yapmışsa çatışma yoktur). Türleri:

TürDurumGit'in gösterimi
İçerikAynı satır bölgesi iki tarafta farklıDosya içinde işaretler; both modified
Değiştir / silBiri dosyayı değiştirmiş, diğeri silmişdeleted by them / deleted by us; dosya çalışma alanında durur
Ekle / ekleİkisi de aynı yolda farklı dosya oluşturmuşboth added; içerik işaretli
Yeniden adlandırBiri dosyayı taşımış, diğeri eski yolda değiştirmişGenelde Git çözer (benzerlik tespiti, 1. ders); iki farklı ad varsa çatışır
İkili dosyaGörsel, PDF, derlenmiş çıktı iki tarafta farklıSatır birleştirme yok; bir tarafı seçersin

Çatışma bir hata değildir — iki kişinin gerçekten aynı yeri düşündüğünün işaretidir ve doğru cevap çoğu kez "ikisini de anlayıp üçüncü bir şey yazmak"tır. Yanlış refleks, işaretleri görür görmez bir tarafı silmektir; kaybedilen taraf birinin sabahı, bazen bir hata düzeltmesidir. Ekle/ekle'nin klasik kamu örneği: iki geliştirici aynı sprintte EF Core migration üretir (7. hat); iki migration dosyası farklı adlarla eklendiği için dosya çatışması yoktur ama ModelSnapshot ikisinde de değişmiştir — çatışma oradadır ve çözümü elle birleştirmek değil, birini geri alıp modeli yeniden üretmektir.

03Anlamsal çatışma: Git'in göremediği

3. dersin notu burada açılır. Ayşe bir metodu SonGunKontrol → SonTarihKontrol olarak yeniden adlandırdı ve tüm çağrıları güncelledi; Can aynı sırada başka bir dosyada SonGunKontrol'ü çağıran yeni bir satır ekledi. İki değişiklik farklı dosyalarda, farklı satırlarda; Git ikisini sessizce birleştirir, merge commit'i sorunsuz atılır — ve proje derlenmez. Daha kötüsü, derlenen versiyonu: Ayşe bir doğrulama kuralının varsayılanını değiştirdi, Can o kurala dayanan yeni bir akış yazdı; merge derlenir, testsiz alanda üretime gider, yanlış çalışır.

Buna anlamsal çatışma denir ve tanım gereği Git göremez: Git satır bölgelerinin üç hâlini karşılaştırır, kodun anlamını bilmez. Görecek olan şey, 15. hattın güven ağıdır: merge sonucu derlenir ve test paketi merge'ün parçası olarak koşar. Bu, "çatışma yok" mesajının "birleştirme doğru" demek olmadığını kabul etmektir. Pratikte iki katman: yerelde merge/rebase sonrası dotnet build && dotnet test (4. dersin üç adımının ortası), sunucuda ise PR'ın hedef dalla birleşmiş hâli üzerinde koşan CI — yalnız dalın kendisi değil (18. hat; platformların "merge queue" özelliği tam bu sorunu hedefler: iki PR ayrı ayrı yeşil olup birlikte kırmızı olabilir).

04İşaretlerin anatomisi ve ortak ata

Merge durunca status "Unmerged paths" listesi verir ve çatışan dosyaya işaretler yazılır:

<<<<<<< HEAD
    if (basvuru.Tarih.Date <= sonGun.Date)
=======
    if (basvuru.Tarih < sonGun.AddDays(1))
>>>>>>> feature/son-gun

Üst blok ours (HEAD — bulunduğun dal), alt blok theirs (birleştirdiğin dal). Şimdi soru: hangisi doğru? Bunu cevaplamak için üçüncü bilgiye ihtiyacın var — satır ortak atada nasıldı? Ancak o zaman "Ayşe neyi değiştirmiş, Can neyi değiştirmiş" görünür. Varsayılan görünüm bunu göstermez; iki sonucu verir, niyeti vermez. Çözüm, bir ayar:

git config --global merge.conflictStyle zdiff3
<<<<<<< HEAD
    if (basvuru.Tarih.Date <= sonGun.Date)
||||||| merged common ancestors
    if (basvuru.Tarih <= sonGun)
=======
    if (basvuru.Tarih < sonGun.AddDays(1))
>>>>>>> feature/son-gun

Orta blok ortak atadır. Artık okunur: iki taraf da aynı hatayı (son gün öğleden sonra reddediliyor — 15-05'in vakası) farklı biçimde düzeltmeye çalışmış; biri tarih karşılaştırıyor, diğeri sınırı ertesi güne taşıyor. Doğru cevap ikisinden birini seçmek değil, ekiple hangi anlamın istendiğine karar vermektir (zaman dilimi ve saat 23:59:59 davranışı ikisinde farklı). diff3 bu görünümü verir; zdiff3 (Git 2.35) ek olarak üç bloğun ortak satırlarını dışarı alıp yalnız gerçekten farklı olan kısmı bırakır. Kıdemli geliştiricinin ilk yaptığı ayarlardan biridir; sözlüde "çatışmada ortak atayı görmek için diff3 kullanırım" cümlesi, komut ezberinden ayrışan cevaptır.

05Çözüm akışı ve geri kapısı

git merge feature/son-gun          # CONFLICT (content): Merge conflict in Basvuru/Kural.cs
git status                         # Unmerged paths: both modified: Basvuru/Kural.cs
git diff                           # yalnız çatışan bölgeler, ++/-- ile birleşik gösterim
                                   # dosyayı aç, işaretleri kaldırıp doğru kodu yaz
dotnet build && dotnet test        # anlamsal kontrol
git add Basvuru/Kural.cs           # "çözdüm" işareti — add, burada çözüm bildirimidir
git merge --continue               # ya da git commit; mesaj hazır gelir

Geri kapısı her aşamada açıktır: git merge --abort çalışma alanını ve index'i merge öncesi hâle döndürür (Git bunu ORIG_HEAD ve MERGE_HEAD ref'leriyle yapar). "Bir şeyleri bozdum" hissinde ilk hamle abort'tur; yarım çözümle commit atmak değil. Rebase'de aynı üçlü vardır: rebase --continue / --skip (bu commit'i atla) / --abort.

Bütün dosya için tek tarafı seçmek gerekiyorsa (ikili dosya, üretilen dosya):

git checkout --ours  -- rapor.pdf     # bulunduğum dalın hâli
git checkout --theirs -- rapor.pdf    # birleştirdiğim dalın hâli
git add rapor.pdf

İki tuzak. Birincisi terminoloji: -X ours (strateji seçeneği) yalnız çatışan bölgelerde bizim tarafı tercih eder, geri kalanı normal birleştirir; -s ours (strateji) ise karşı tarafın tüm değişikliğini atar ve yalnız "birleşti" kaydı bırakır — tamamen farklı iki şey, tek harf farkla. İkincisi ve daha sinsi olanı: rebase'de ours/theirs yer değiştirir. Rebase, senin commit'lerini hedef dalın üstüne yeniden oynatır; oynatma sırasında HEAD hedef daldır, bu yüzden "ours" = hedef dal (main), "theirs" = senin commit'in. Merge'deki refleksle --ours seçen kişi kendi işini atar. Kural: rebase'de önce status'a bak, sonra taraf seç.

Git'in çatışmayı nasıl sakladığını görmek ayrımı netleştirir: index, çatışan dosya için üç girdi tutar — stage 1 ortak ata, stage 2 ours, stage 3 theirs. git ls-files -u üçünü listeler; git show :1:Basvuru/Kural.cs atadaki tam dosyayı verir. İşaretler, bu üç aşamanın çalışma alanına yazılmış özetidir; add üç girdiyi tek girdiye indirir — "çözüldü" bilgisinin fiziksel anlamı budur.

06mergetool ve rerere

İşaretleri metin editöründe düzenlemek küçük çatışmalar için yeterlidir; büyüklerde üç panelli araç okunabilirlik sağlar. git mergetool, yapılandırılmış aracı çatışan her dosya için açar; VS Code'un üç yönlü merge editörü (1.69+) tipik seçimdir:

git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
git config --global mergetool.keepBackup false

Son satır, mergetool'un bıraktığı .orig yedek dosyalarının depoya sızmasını önler (2. dersin "çöp girmez" kuralı). Araç ne olursa olsun ilke aynı: üç görünüm — ata, ours, theirs — ve sonucu sen yazarsın.

rerere ("reuse recorded resolution") daha az bilinen ama uzun ömürlü dalla çalışanın hayatını değiştiren özelliktir. Açıkken Git, çözdüğün her çatışmanın "önce"sini ve "sonra"sını kaydeder; aynı çatışma yeniden çıkarsa çözümü otomatik uygular. Ne zaman aynı çatışma yeniden çıkar? Özellik dalını main'e her hafta rebase ediyorsan (6. ders) her seferinde; ya da bir dalı önce merge ile denedin, abort ettin, sonra rebase ile yaptın. git config --global rerere.enabled true — bir kez açılır, unutulur. Otomatik uygulanan çözüm yine add bekler (kontrol sende); yanlış kaydedilen çözüm git rerere forget dosya ile silinir.

07Çatışmayı küçültmek: araç değil, ritim

3. dersin sonucu buraya bağlanır: çatışmanın büyüklüğü, iki dalın ortak atadan ne kadar uzaklaştığıyla orantılıdır. Dolayısıyla asıl strateji birleştirmede değil, çalışma ritmindedir:

  • Küçük PR, sık entegrasyon. Bir haftalık dal yerine bir günlük dal; büyük iş, kapalı özellik bayrağı arkasında küçük parçalarla. Çatışma sıklığı artmaz, her biri küçülür — ve küçük çatışma, anlamı hâlâ akıldayken çözülür.
  • Ana dalı dala sık çek. Günde bir git rebase main (ya da merge — 7. dersin kararı); çatışmayı hafta sonunda toplu değil, günlük küçük dozlarda karşıla. rerere bu ritmin sürtünmesini alır.
  • Biçimlendirmeyi ayır ve makineye devret. Tüm dosyayı yeniden biçimlendiren commit, o dosyaya dokunan her açık dalla çatışır. 15-10'un kararı: ortak biçimlendirici + .editorconfig depoda; toplu biçimlendirme ayrı commit'te, ekip haberdar ve dallar boşken (2. dersin blame ignore-revs dosyasına da eklenir).
  • Üretilen dosyayı elle birleştirme. package-lock.json, packages.lock.json, migration snapshot'ı, derlenmiş çıktı: bir tarafı seç, sonra üreten komutu yeniden çalıştır (npm install, dotnet restore, migration yeniden üret). Elle birleştirilmiş kilit dosyası, tutarsız bağımlılık ağacının en sık nedenidir (12. hat).
  • Birleştirme kuralını dosyaya göm. .gitattributes'ta CHANGELOG.md merge=union her iki tarafın satırlarını çatışmasız üst üste koyar — değişiklik günlüğü gibi "her iki ekleme de geçerli" dosyalar için; *.pdf -diff ikili dosyayı metin diff'inden çıkarır. Kod dosyalarına uygulanmaz.
  • Sık çatışan dosyayı tasarım sinyali olarak oku. Herkesin dokunduğu tek dev dosya (3.000 satırlık Startup, tek Sabitler.cs) 15-10'daki "sık değişen × karmaşık" haritasında da kırmızıdır; çözüm merge becerisi değil, dosyayı bölmektir (4. hat).

Bu listenin toplamı 18. hatta "sürekli entegrasyon" adını alır: entegrasyon o kadar sık olsun ki hiçbir zaman büyük, hiçbir zaman korkutucu olmasın.

08Mini alıştırma — çatışmayı üret, oku, çöz

Yaklaşık 20 dakika · 2-3. derslerin deposu

  1. Ayar. git config --global merge.conflictStyle zdiff3 ve git config --global rerere.enabled true.
  2. Üret. main'de üç satırlık kural.txt commit'le. feature/a aç, 2. satırı değiştir, commit. main'e dön, 2. satırı farklı değiştir, commit. Ayrıca 3. satırı yalnız main'de değiştir.
  3. Oku. git merge feature/a → çatışma. Dosyayı aç: 2. satırda üç blok (ata dahil), 3. satırın çatışmasız birleştiğini gör. git ls-files -u ile üç aşamayı listele; git show :1:kural.txt ile atayı yazdır.
  4. Abort. git merge --abort; git status temiz mi? Tekrar merge et; bu kez çöz — iki değişikliğin niyetini birleştiren üçüncü bir satır yaz — add, merge --continue. git log --graph --oneline.
  5. rerere. Merge commit'ini git reset --hard HEAD~1 ile geri al (6. ders; yerel, güvenli). Yeniden git merge feature/a: Git çözümü otomatik uyguladı mı? ("Resolved ... using previous resolution"). Sonucu kontrol edip add + commit.
  6. Rebase'de taraflar. feature/b aç, aynı satırı değiştir; main'de de değiştir. feature/b'de git rebase main → çatışma. git checkout --ours -- kural.txt yapıp içeriğe bak: hangi taraf geldi? --theirs ile karşılaştır; rebase --abort.
  7. Anlamsal. Bir dosyada fonksiyon adı değiştir (tüm çağrılarla) bir dalda; başka dalda eski adla yeni çağrı ekle. Merge: çatışma yok. Derle. Sonucu bir cümleyle yaz: Git neyi göremedi, ne yakaladı?

09Kendini sına

Merge çatışması nedir, hangi türleri vardır? Çatışma neden bir hata değildir ve "bir tarafı silmek" refleksi neden yanlıştır?

Çatışma, üç yönlü birleştirmenin karar veremediği bölgedir: ortak ataya göre iki taraf da aynı bölgeyi değiştirmiş ve değişiklikler aynı değil. Farklı satırlar çatışmaz (sessizce birleşir), birebir aynı değişiklik de çatışmaz. Türleri: içerik (aynı bölge farklı — dosya içinde işaretler, "both modified"), değiştir/sil (biri değiştirmiş biri silmiş — "deleted by them/us"), ekle/ekle (aynı yolda iki farklı dosya), yeniden adlandır (Git benzerlikle çoğunu çözer, iki farklı ad çatışır) ve ikili dosya (satır birleştirme yok, bir taraf seçilir). Çatışma hata değildir; iki kişinin gerçekten aynı yeri düşündüğünün işaretidir ve doğru cevap çoğu kez ikisini de anlayıp üçüncü bir şey yazmaktır — örnekte iki taraf aynı hatayı farklı anlamla düzeltmiştir, ekiple hangi anlamın istendiğine karar vermek gerekir. Bir tarafı silmek refleksi, kaybedilen tarafın birinin sabahı ya da bir hata düzeltmesi olması nedeniyle yanlıştır. Kamu örneği: iki geliştiricinin aynı sprintte ürettiği EF Core migration'ları dosya olarak çatışmaz ama ModelSnapshot çatışır; çözüm elle birleştirmek değil, birini geri alıp modeli yeniden üretmektir.

Anlamsal çatışma nedir, Git neden göremez ve ne yakalar? İşaretlerdeki HEAD/theirs blokları neyi gösterir; diff3/zdiff3 görünümü neden gereklidir?

Anlamsal çatışma, iki dalın farklı satırları değiştirdiği için Git'in sorunsuz birleştirdiği ama sonucun derlenmediği ya da yanlış çalıştığı durumdur: bir taraf metodu yeniden adlandırmış, diğeri eski adla yeni çağrı eklemiş; ya da biri varsayılanı değiştirmiş, diğeri eski varsayılana dayanan akış yazmış. Git satır bölgelerinin üç hâlini karşılaştırır, kodun anlamını bilmez — göremez. Yakalayan, merge sonucunun derlenmesi ve test paketinin merge'ün parçası olarak koşmasıdır: yerelde entegrasyon sonrası build + test, sunucuda PR'ın hedef dalla birleşmiş hâli üzerinde CI (merge queue tam bu sorunu hedefler: iki PR ayrı ayrı yeşil, birlikte kırmızı olabilir). "Çatışma yok" mesajı "birleştirme doğru" demek değildir. İşaretlerde üst blok ours (HEAD, bulunduğun dal), alt blok theirs (birleştirilen dal). Varsayılan görünüm iki sonucu verir ama niyeti vermez: hangisinin doğru olduğuna karar vermek için satırın ortak atada nasıl olduğunu görmek gerekir — ancak o zaman kimin neyi değiştirdiği okunur. merge.conflictStyle diff3 ortak ata bloğunu (|||||||) ekler; zdiff3 (2.35) üç bloğun ortak satırlarını dışarı alıp yalnız gerçek farkı bırakır. Kıdemli geliştiricinin ilk ayarlarından biridir.

Çözüm akışını adım adım anlat. --ours/--theirs rebase'de neden yer değiştirir? -X ours ile -s ours farkı nedir? Index'in üç aşaması ne anlama gelir?

Akış: merge çatışma bildirir; status "Unmerged paths" listeler; diff yalnız çatışan bölgeleri birleşik gösterimle verir; dosya açılır, işaretler kaldırılıp doğru kod yazılır; build ve test koşulur (anlamsal kontrol); add ile "çözüldü" bildirilir — add burada çözüm bildirimidir; merge --continue ya da commit ile hazır mesajla merge commit'i atılır. Her aşamada merge --abort (ORIG_HEAD/MERGE_HEAD ile) merge öncesi hâle döndürür; rebase'de continue/skip/abort üçlüsü aynıdır. Bütün dosya için tek taraf: checkout --ours/--theirs -- dosya + add. Rebase, senin commit'lerini hedef dalın üstüne yeniden oynatır; oynatma sırasında HEAD hedef daldır, bu yüzden ours = hedef dal (main), theirs = senin commit'in — merge'deki refleksle --ours seçen kendi işini atar; önce status'a bakılır. -X ours strateji seçeneğidir: yalnız çatışan bölgelerde bizim tarafı tercih eder, geri kalanı normal birleştirir; -s ours stratejidir: karşı tarafın tüm değişikliğini atar, yalnız "birleşti" kaydı bırakır — tek harf farkla tamamen farklı iki şey. Index çatışan dosya için üç girdi tutar: stage 1 ortak ata, stage 2 ours, stage 3 theirs; ls-files -u listeler, show :1:dosya atayı verir; işaretler bu üç aşamanın çalışma alanına yazılmış özetidir ve add üçünü tek girdiye indirir.

rerere nedir, ne zaman işe yarar? Çatışmaları küçültmek için hangi çalışma pratikleri uygulanır ve üretilen/kilit dosyaları neden elle birleştirilmez?

rerere ("reuse recorded resolution"), açıkken çözülen her çatışmanın önce/sonra hâlini kaydeder ve aynı çatışma yeniden çıkarsa çözümü otomatik uygular (yine add bekler; yanlış kayıt rerere forget ile silinir). İşe yaradığı yer, aynı çatışmanın tekrar çıktığı durumlardır: özellik dalını main'e her hafta rebase etmek, merge'ü abort edip rebase ile yeniden yapmak, uzun ömürlü dallar. Çatışmayı küçültme araçta değil ritimdedir, çünkü çatışma büyüklüğü ortak atadan uzaklaşmayla orantılıdır: küçük PR ve sık entegrasyon (bir günlük dal; büyük iş özellik bayrağı arkasında parçalarla) — çatışma sıklığı artmaz, her biri küçülür ve anlam akıldayken çözülür; ana dalı dala günlük çekmek (rebase ya da merge) — toplu değil günlük doz; biçimlendirmeyi makineye devredip toplu biçimlendirmeyi ayrı commit'te, dallar boşken yapmak (blame ignore-revs'e de eklenir); sık çatışan dev dosyayı tasarım sinyali olarak okuyup bölmek. Üretilen ve kilit dosyaları (package-lock.json, packages.lock.json, migration snapshot, derlenmiş çıktı) elle birleştirilmez: bir taraf seçilir, üreten komut yeniden çalıştırılır — elle birleştirilmiş kilit dosyası tutarsız bağımlılık ağacının en sık nedenidir. .gitattributes'ta merge=union "her iki ekleme geçerli" dosyalar (CHANGELOG) için çatışmasız üst üste koyar; kod dosyalarına uygulanmaz. Toplam, sürekli entegrasyonun tanımıdır: entegrasyon hiçbir zaman büyük olmasın.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) Merge conflict nedir, hangi durumda oluşur? (2) Çatışma işaretleri (<<<<<<<, =======, >>>>>>>) neyi ifade eder? (3) Bir çatışma nasıl çözülür; adımları yazınız. (4) git merge --abort ne yapar? (5) Üç yönlü birleştirmede ortak atanın rolü nedir? (6) İkili (binary) dosyalarda çatışma nasıl ele alınır? (7) Çatışmaları azaltmak için hangi çalışma pratikleri önerilir? (8) git mergetool ne işe yarar? (9) Rebase sırasında çatışma çıkarsa süreç nasıl devam eder? (10) Git'in tespit edemediği çatışma türü var mıdır?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Tanım — "Çatışma, üç yönlü birleştirmede iki tarafın ortak ataya göre aynı bölgeyi farklı değiştirdiği yerdir; Git karar veremez, bana bırakır. Hata değil, iki kişinin aynı yeri düşündüğünün işaretidir." (2) Okuma — "İlk işim ortak atayı görmektir: zdiff3 ile kimin neyi değiştirdiğini okurum; çoğu kez doğru cevap bir tarafı seçmek değil, iki niyeti birleştiren üçüncü satırı yazmaktır." (3) Akış — "Aç, düzelt, derle ve test et, add ile çözüldü de, commit. Emin değilsem --abort ile temiz noktaya dönerim. Rebase'de ours/theirs yer değiştirir, önce status'a bakarım." (4) Görünmeyen çatışma — "Git anlamsal çatışmayı göremez — derlenen ama bozuk merge. Bu yüzden merge sonucu üzerinde test koşmak zorunludur; CI'da PR'ı hedef dalla birleşmiş hâliyle test ederim." (5) Kıdem vuruşu — "Asıl strateji birleştirmede değil ritimdedir: küçük PR, ana dalı günlük çekme, biçimlendirmeyi makineye devretme, kilit ve üretilen dosyaları elle değil yeniden üreterek çözme; uzun ömürlü dalda rerere aynı çatışmayı iki kez çözdürmez. Sık çatışan dosya bir tasarım sinyalidir — bölerim."