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;--abortgeri 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
mergetoolve 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ür | Durum | Git'in gösterimi |
|---|---|---|
| İçerik | Aynı satır bölgesi iki tarafta farklı | Dosya içinde işaretler; both modified |
| Değiştir / sil | Biri 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ır | Biri dosyayı taşımış, diğeri eski yolda değiştirmiş | Genelde Git çözer (benzerlik tespiti, 1. ders); iki farklı ad varsa çatışır |
| İkili dosya | Gö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
+
.editorconfigdepoda; 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'taCHANGELOG.md merge=unionher 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 -diffikili 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, tekSabitler.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
- Ayar.
git config --global merge.conflictStyle zdiff3vegit config --global rerere.enabled true. - Üret. main'de üç satırlık
kural.txtcommit'le.feature/aaç, 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. - 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 -uile üç aşamayı listele;git show :1:kural.txtile atayı yazdır. - Abort.
git merge --abort;git statustemiz 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. - rerere. Merge commit'ini
git reset --hard HEAD~1ile geri al (6. ders; yerel, güvenli). Yenidengit merge feature/a: Git çözümü otomatik uyguladı mı? ("Resolved ... using previous resolution"). Sonucu kontrol edipadd+ commit. - Rebase'de taraflar.
feature/baç, aynı satırı değiştir; main'de de değiştir.feature/b'degit rebase main→ çatışma.git checkout --ours -- kural.txtyapıp içeriğe bak: hangi taraf geldi?--theirsile karşılaştır;rebase --abort. - 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."
