Metin Karşılaştırma (Diff)
İki metni satır satır karşılaştırın ve tam olarak neyin değiştiğini görün. Boşluk ve büyük harf duyarlılığı seçilebilir, değişen satırlarda kelime bazlı görünüm sunar.
İki tarafa da metin girin, satır satır fark burada görünecek.
Özet (TL;DR)
- Windows CRLF, Linux ve macOS ise LF kullanır. Aynı dosya sadece satır sonu değiştiği için baştan sona değişmiş görünebilir.
- Bu araç karşılaştırmadan önce CRLF ve CR satır sonlarını LF'e indiriyor, o yüzden satır sonu farkı tek başına diff üretmez.
- LCS tabanlı hizalama taşınan bir bloğu "taşındı" diye işaretlemez: bir silme ve bir ekleme olarak gösterir.
- Türkçe metinde "ğ" iki farklı bayt dizisiyle yazılabilir. Ekranda aynı görünen iki satır bu yüzden farklı çıkabilir.
Hiçbir şey değişmedi ama dosya değişmiş görünüyor
Bir pull request açıyorsunuz, tek bir fonksiyonu düzelttiniz, ama diff ekranında 340 satır kırmızı ve 340 satır yeşil duruyor. Kodu satır satır okuyorsunuz, gerçekten hiçbir şey değişmemiş. Bu tabloyu bir kez gören herkes aynı şeyi yaşıyor: değişiklik gözle görülmüyor çünkü değişen şey görünmeyen karakterler.
Bu durumun neredeyse her seferinde iki sebebi oluyor. Ya satır sonu karakteri değişmiştir, ya da editörünüz kaydederken satır sonlarındaki boşlukları temizlemiştir. İkisi de ekranda hiçbir iz bırakmaz, ikisi de dosyanın her satırını farklı yapar.
Satır sonu meselesi, yani CRLF ve LF
Windows bir satırı iki karakterle bitirir: carriage return ve line feed, yani \r\n. Linux ve macOS tek karakter kullanır, \n. Dosyayı Windows'ta açıp kaydeden bir arkadaşınız tek bir harfe dokunmasa bile, dosyanın her satırına fazladan bir \r girer. Git için bu her satırın değişmesi demektir.
# Diff ekranında hiçbir fark görünmüyor ama Git dosyayı değişmiş sayıyor$ git diff --stat src/utils/format.ts | 340 ++++++++++++++++----------------- # Sebebi gizli karakterler. cat -A ile görünür hale gelir:$ cat -A src/utils/format.ts | head -2export function slugify(input: string) {^M$ return input.trim();^M$# ^^^ CRLF satır sonu ^^^ LF olsaydı sadece $ olurdu # Kalıcı çözüm, depoya bir .gitattributes koymak:$ echo "* text=auto eol=lf" > .gitattributes$ git add --renormalize .git config core.autocrlf ayarı kişisel bir yamadır: sadece o makinede çalışır, ekibe yeni katılan biri aynı sorunu baştan yaşar. .gitattributes ise depoya girer ve herkes için geçerli olur. Bir projede satır sonu kavgası tekrar tekrar çıkıyorsa, çözüm bu dosyadadır.
Bu araçta satır sonu farkını hiç görmezsiniz. Metinler bölünmeden önce \r\n ve tek başına \r karakterleri \n haline getiriliyor, ayrıca sondaki fazladan boş satır atılıyor. Böylece iki tarafı iki farklı işletim sisteminden yapıştırdığınızda hayalet bir "tüm dosya değişti" sonucu almazsınız.
Taşınan blok neden iki kere görünüyor
Diff hesabı en uzun ortak alt dizi, yani LCS üzerinden yürüyor. Araç iki metnin satırlarını alıp aynı sırayla ilerleyen en uzun ortak listeyi buluyor; o listede olan her satır "aynı", olmayan her satır ise ya silinmiş ya eklenmiş sayılıyor. Yaklaşımın tek kuralı sıranın korunması.
Bir fonksiyonu dosyanın altından üstüne taşıdığınızda sıra bozulduğu için o satırlar ortak alt diziye giremiyor. Sonuç: aynı blok bir kere eski yerinde silinmiş, bir kere yeni yerinde eklenmiş olarak çıkıyor. Bu bir hata değil, satır tabanlı diff'in tanımı. Kod incelemesinde "bu blok sadece yer değiştirdi" cümlesini yorum olarak yazmanız gerekmesinin sebebi de bu.
LCS'nin asıl kazandırdığı şey başka yerde: araya tek bir satır eklendiğinde sonrasının kaymamasını sağlıyor. İki metni ortak bir sayaçla yan yana yürüten basit bir karşılaştırma, dosyanın başına eklenen tek satır yüzünden geri kalan her satırı değişmiş gösterir. İnternette bulunan "iki metni yapıştır" araçlarının çoğunda gördüğünüz o anlamsız çıktının sebebi tam olarak bu.
Bir satır hem silinmiş hem eklenmiş göründüğünde, yani değiştirilmiş bir satırda, silme satırı önce yazılıyor. Yan yana görünümde eskinin üstte yeninin altta olması beklendiği için sıralama buna göre sabitlenmiş durumda.
Bilgi
Satır sonu boşlukları ve satır içi karşılaştırma
Satır sonundaki boşluk, kod incelemesinde en çok gürültü üreten ikinci şey. Birçok ekip editörde "kaydederken satır sonu boşluklarını temizle" ayarını açtığı için, ayarı açık olan biri bir dosyaya dokunduğunda o dosyanın dokunulmamış satırları da değişmiş görünüyor. Ayarı ekipçe aynı tarafa çekmek bu gürültüyü tamamen bitirir.
Boşluk farkını geçici olarak susturmak isterseniz "boşlukları yoksay" seçeneği var. Bu seçenek sadece eşleştirmeyi etkiliyor: ekranda gördüğünüz satır her zaman orijinal metin, boşlukları sıkıştırılmış hali değil. Yazmadığınız bir belgeyi size göstermemek bu aracın temel kurallarından biri.
Boşluk yoksayma ayarını kalıcı olarak açık bırakmak ise iyi bir fikir değil. Python'da girinti sözdiziminin parçası, YAML'da bir anahtarın hangi bloğa ait olduğunu girinti belirliyor, Makefile'da sekme ile boşluk aynı şey değil. Bu dosyalarda boşluk farkı gürültü değil, aradığınız değişikliğin kendisi olabilir.
Bir satırın nesi değiştiğini anlamak içinse satır diff'i çoğu zaman yetmiyor. Uzun bir cümlede tek kelime değiştiyse satır kırmızı ve yeşil olarak iki kere karşınıza çıkıyor ve farkı gözle aramak zorunda kalıyorsunuz. Satır içi karşılaştırma o satırı kelimelere ve aradaki boşluklara bölüp aynı LCS'yi tekrar çalıştırıyor, böylece değişen tek kelime doğrudan işaretleniyor.
Aynı görünen iki Türkçe metnin farklı çıkması
Unicode'da bir harf iki şekilde yazılabiliyor. "ğ" tek bir kod noktası olarak durabilir, ya da "g" harfi artı üstüne gelen birleştirici işaret olarak iki kod noktasına yayılabilir. İkisi ekranda tıpatıp aynı görünür, dosyada farklı baytlardır. Buna sırasıyla birleşik (NFC) ve ayrışık (NFD) biçim deniyor.
| Kaynak | Tipik biçim | Diff'te sonucu |
|---|---|---|
| Windows ve Linux editörleri | Birleşik (NFC) | Beklenen davranış |
| macOS dosya adları | Ayrışık (NFD) | Aynı ada sahip iki satır farklı çıkar |
| Kopyala yapıştır ile gelen PDF metni | Karışık | Bazı harfler eşleşmez |
| Veritabanından çekilen kayıt | Sütun ayarına bağlı | Kaynağa göre değişir |
Bu araç bilerek normalize() çağırmıyor. Sebebi şu: normalize etmek farkı gizler ve siz de dosyanızda gerçekten bir sorun olduğunu hiç öğrenemezsiniz. İki satır ekranda aynı görünüp burada farklı çıkıyorsa, elinizde gerçek bir Unicode sorunu var demektir ve muhtemelen aynı sorun arama, sıralama ve karşılaştırma yapan her yerde patlıyor. Kaynağı tek bir biçime çekmek doğru çözüm, diff aracını sağır etmek değil.
Sıkça Sorulan Sorular
- Neden her satır değişmiş görünüyor?
- Büyük ihtimalle satır sonu karakteri değişmiştir. Dosya Windows'ta CRLF ile kaydedilmişse Linux tarafındaki LF sürümüne göre her satırı farklıdır. Depoya
* text=auto eol=lfiçeren bir.gitattributeskoyupgit add --renormalize .çalıştırmak kalıcı çözümdür. - Boşluk farklarını yoksayabilir miyim?
- Evet, "boşlukları yoksay" seçeneği ardışık boşlukları tek boşluğa indirip satır başındaki ve sonundaki boşlukları kırparak karşılaştırıyor. Bu sadece eşleştirmeyi etkiler, ekranda gösterilen metin her zaman orijinal halidir.
- Yapıştırdığım metin bir sunucuya gidiyor mu?
- Hayır. Karşılaştırma tamamen tarayıcınızda çalışıyor, hiçbir istek gönderilmiyor. Sayfa yüklendikten sonra bağlantınızı kesseniz bile araç çalışmaya devam eder.
- Dosya yükleyip karşılaştırabilir miyim?
- Araç iki metin kutusu üzerinden çalışıyor, dosya içeriğini kutulara yapıştırmanız gerekiyor. Depodaki iki dosyayı karşılaştırmak için
git diffya dadiff -udaha doğrudan bir yol, bu araç daha çok panoya kopyaladığınız iki parçayı hızlıca kıyaslamak için. - Yerini değiştirdiğim satır neden iki kere çıktı?
- Satır tabanlı diff sıraya bakar. Taşınan blok ortak alt diziye giremediği için eski konumunda silme, yeni konumunda ekleme olarak görünür. "Taşındı" diye ayrı bir işaret üretmez, bunu yorumlayan sizsiniz.