Mert Özen Her Satırda Daha İleri
DevOps Git & GitHub — #07

Git diff ile Değişiklikleri Karşılaştırmak

Mert Özen 6 Kas 2026 9 dk 1 görüntülenme
Git diff ile Değişiklikleri Karşılaştırmak

git diff'in working tree, index ve HEAD arasında hangi karşılaştırmayı yaptığını, diff çıktısının nasıl okunduğunu, --stat, --name-only ve --word-diff gibi özet görünümleri, commit'ler arası diff'i ve iki nokta ile üç nokta (--merge-base) farkını gerçek çıktılarla görüyoruz.

Sürüm notu: Bu yazıdaki komutlar Git 2.54 ile denenmiştir.

Önce Tahmin Et

Önceki yazılarda büyüttüğümüz bookstore repo'sundayız. src/books.js dosyasında Dune'un fiyatını 12'den 14'e çıkarıyorsun ve dosyayı git add ile staging area'ya alıyorsun. Commit atmadan, aynı dosyada bu kez Neuromancer'ın fiyatını 9'dan 11'e çekiyorsun. Kısa durum çıktısı şöyle:

$ git status -s
MM src/books.js

İki M harfi yan yana: soldaki index'teki değişikliği, sağdaki working tree'deki değişikliği anlatıyor. Şimdi soru: bu noktada düz git diff yazarsan ekranda hangi fiyat değişikliğini görürsün? Dune'u mu, Neuromancer'ı mı, ikisini birden mi? Cevabı bir sonraki bölümde, ama önce kendi tahminini bir kenara yaz.

Üç Alan, Üç Karşılaştırma

Git'te bir dosyanın aynı anda üç hâli olabilir: son commit'teki hâli, index'teki hâli ve diskteki hâli. git diff bu üç hâlden ikisini yan yana koyar. Hangi ikisi olduğunu verdiğin seçenek belirler.

$ git diff
diff --git a/src/books.js b/src/books.js
index e7fa590..4e89fb6 100644
--- a/src/books.js
+++ b/src/books.js
@@ -1,6 +1,6 @@
 const books = [
   { id: 1, title: "Dune", price: 14 },
-  { id: 2, title: "Neuromancer", price: 9 },
+  { id: 2, title: "Neuromancer", price: 11 },
 ];

 function findBook(id) {

Tahminin Neuromancer'sa doğru bildin. Parametresiz git diff, working tree'yi index ile karşılaştırır; yani "henüz stage'lemediğin değişiklikleri" gösterir. Dune zaten index'e girdiği için bu karşılaştırmada fark olarak görünmüyor, satırı değişmemiş bir context satırı olarak duruyor.

Stage'lediklerini görmek için --staged kullanırsın. Bu sefer index son commit'le, yani HEAD ile karşılaştırılır:

$ git diff --staged
diff --git a/src/books.js b/src/books.js
index 300383a..e7fa590 100644
--- a/src/books.js
+++ b/src/books.js
@@ -1,5 +1,5 @@
 const books = [
-  { id: 1, title: "Dune", price: 12 },
+  { id: 1, title: "Dune", price: 14 },
   { id: 2, title: "Neuromancer", price: 9 },
 ];
 

--cached aynı şeyin eski adıdır; ikisi birebir aynı çalışır. Commit'ten hemen önce git diff --staged yazmayı alışkanlık hâline getir. Commit'e girecek olan tam olarak budur, fazlası değil.

Üçüncü karşılaştırma ikisinin toplamı: HEAD ile working tree. Index'i atlar ve son commit'ten beri diskte neyin değiştiğini tek seferde gösterir:

$ git diff HEAD
diff --git a/src/books.js b/src/books.js
index 300383a..4e89fb6 100644
--- a/src/books.js
+++ b/src/books.js
@@ -1,6 +1,6 @@
 const books = [
-  { id: 1, title: "Dune", price: 12 },
-  { id: 2, title: "Neuromancer", price: 9 },
+  { id: 1, title: "Dune", price: 14 },
+  { id: 2, title: "Neuromancer", price: 11 },
 ];

 function findBook(id) {

Bir kargo benzetmesi işe yarar. Masandaki eşyalar working tree, koliye koyduğun eşyalar index, kapıdan çıkmış koli de commit. git diff masayla koliyi, git diff --staged koliyle son giden koliyi, git diff HEAD ise masayla son giden koliyi karşılaştırır.

Diff Çıktısını Okumak

Çıktı ilk bakışta kalabalık görünür ama her satırın tek bir görevi var. diff --git a/src/books.js b/src/books.js satırı hangi dosyanın karşılaştırıldığını söyler; a/ eski tarafı, b/ yeni tarafı temsil eder. index e7fa590..4e89fb6 100644 satırındaki iki kısa hash, dosyanın eski ve yeni içeriğine karşılık gelen blob object'leridir. Commit'in içini anlattığımız yazıdaki git cat-file -p ile bu hash'lerin içine bakabilirsin. 100644 ise dosya modu: çalıştırılabilir olmayan sıradan bir dosya.

--- ve +++ satırları eski ve yeni tarafın adını bir kez daha verir. Asıl bilgi @@ -1,6 +1,6 @@ ile başlayan hunk başlığında. "Eski dosyanın 1. satırından başlayan 6 satır, yeni dosyanın 1. satırından başlayan 6 satıra dönüştü" diye okunur. Altındaki satırlar üç türlüdür: başında boşluk olanlar değişmemiş context satırlarıdır, - ile başlayanlar silinmiş, + ile başlayanlar eklenmiştir. Git, değişikliğin yerini rahat bulman için varsayılan olarak üç satır context gösterir; -U1 ya da -U0 ile bunu daraltabilirsin.

Git'in "satır değişti" diye bir kavramı yok. Bir satırın değişmesi, eski satırın silinip yenisinin eklenmesi olarak gösterilir. Bu yüzden tek bir sayıyı değiştirdiğinde bile bir - bir + satırı görürsün. Bu çıktının genel adı patch'tir ve bu biçim, e-postayla patch gönderen ya da pull request'te değişiklik inceleyen herkesin ortak dilidir.

Özet Görünümler

Fiyat değişikliklerini commit'leyip devam edelim. Bu kez src/cart.js içindeki cartTotal fonksiyonuna bir indirim parametresi ekliyorsun ve README'deki cümleyi biraz değiştiriyorsun. İki dosya değişince satır satır diff uzar. Önce resmin bütününe bakmak istersen:

$ git diff --stat
 README.md   | 2 +-
 src/cart.js | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)
$ git diff --name-only
README.md
src/cart.js
$ git diff --name-status
M	README.md
M	src/cart.js

--stat her dosya için kaç satırın eklenip silindiğini küçük bir çubukla gösterir; büyük bir değişikliği incelemeye nereden başlayacağını seçmek için idealdir. --name-only yalnızca dosya adlarını verir ve script yazarken çok işe yarar: "bu commit'te hangi dosyalar değişti, sadece onlar için lint çalıştır" gibi. --name-status ise başa bir harf ekler: M değişmiş, A eklenmiş, D silinmiş, R yeniden adlandırılmış. CI/CD pipeline'larında "sadece docs/ değiştiyse testleri atla" tarzı kararlar çoğunlukla bu çıktılardan birine bakılarak verilir.

Kelime Kelime: --word-diff

README gibi düz yazı dosyalarında satır bazlı diff yorucudur. Uzun bir paragrafta tek kelime değiştiğinde bütün satır silinmiş ve yeniden yazılmış gibi görünür:

$ git diff README.md
diff --git a/README.md b/README.md
index eab8800..027a113 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
 # Bookstore

-A tiny bookstore app used in the Git & GitHub series.
+A small bookstore app used in the Git and GitHub series.

Aynı değişikliği kelime düzeyinde görmek için:

$ git diff --word-diff README.md
diff --git a/README.md b/README.md
index eab8800..027a113 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
# Bookstore

A [-tiny-]{+small+} bookstore app used in the Git [-&-]{+and+} GitHub series.

[-...-] silineni, {+...+} ekleneni işaretler. Renkli bir terminalde --word-diff=color daha da okunaklıdır; köşeli parantezler kalkar, silinen kırmızı, eklenen yeşil görünür. Dokümantasyon, Markdown ya da çeviri dosyalarında bu seçenek gözünü çok yormaz.

Commit'ler Arasında diff

git diff'e iki commit verirsen working tree ve index devreden çıkar; Git yalnızca o iki snapshot'ı karşılaştırır. En sık kullandığın biçim, son commit'in getirdiği değişikliği görmek:

$ git diff HEAD~1 HEAD
diff --git a/src/books.js b/src/books.js
index 4e89fb6..a7699ec 100644
--- a/src/books.js
+++ b/src/books.js
@@ -1,6 +1,7 @@
 const books = [
   { id: 1, title: "Dune", price: 14 },
   { id: 2, title: "Neuromancer", price: 11 },
+  { id: 3, title: "Snow Crash", price: 10 },
 ];

 function findBook(id) {

HEAD~1, HEAD'in bir önceki commit'i demek. Yerine git log --oneline'da gördüğün kısa hash'leri de yazabilirsin. Karşılaştırmayı tek bir dosyaya daraltmak için dosya yolunu -- işaretinden sonra verirsin:

$ git diff 7ff0565 HEAD -- README.md
diff --git a/README.md b/README.md
index eab8800..027a113 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
 # Bookstore

-A tiny bookstore app used in the Git & GitHub series.
+A small bookstore app used in the Git and GitHub series.

Arada kaç commit olduğunun önemi yok. Git ara adımlara bakmaz, iki ucun içeriğini karşılaştırır. Dosya ilk commit'ten bu yana beş kez değişip sonunda başladığı yere dönmüş olsaydı, bu komut hiçbir fark göstermezdi.

İki Nokta mı, Üç Nokta mı?

Bu bölüm için bir branch (dal) gerekiyor. Branch'leri bir sonraki yazıda ayrıntısıyla ele alacağız; şimdilik bir commit dizisinin ayrı bir kolu olduğunu bilmen yeter. README'yi commit'ledikten sonra discount adında bir branch açtık ve indirim değişikliğini orada commit'ledik. main'e dönüp kitap listesine Snow Crash'i ekledik. Tablo şöyle:

$ git log --oneline --graph --all
* 5bcf046 Add Snow Crash
| * 86303f4 Add discount to cart total
|/
* 6651492 Reword README
* a5a7a57 Update book prices
* 6dde20b Add cart total
* f9b9660 Add .gitignore
* 02ea975 Add book list
* 7ff0565 Add README

Şimdi "discount branch'i neyi değiştiriyor?" sorusunu iki farklı yolla soralım:

$ git diff main..discount --stat
 src/books.js | 1 -
 src/cart.js  | 4 ++--
 2 files changed, 2 insertions(+), 3 deletions(-)
$ git diff main...discount --stat
 src/cart.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

İki noktalı biçim git diff main discount ile aynıdır: iki ucun bugünkü hâlini karşılaştırır. discount branch'inde Snow Crash olmadığı için çıktı sanki bu branch kitabı siliyormuş gibi bir - satırı gösterir. Oysa discount kimsenin kitabını silmedi; sadece main'deki yeni commit'i henüz görmedi.

Üç noktalı biçim ise önce iki branch'in ortak atasını bulur ve o noktadan discount'a kadar olan değişikliği gösterir. "Bu branch ayrıldığından beri ne yaptı?" sorusunun doğru cevabı budur. Pull request ekranında gördüğün diff de bu mantıkla çalışır. Ortak atayı kendin görmek istersen:

$ git merge-base main discount
66514929b4fe53a14d1a73a9334836979ca13ccf
$ git diff --merge-base main discount --stat
 src/cart.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

--merge-base, üç noktanın uzun ve daha okunaklı yazımıdır ve Git 2.30'dan beri var. Script'lerde niyetini açıkça söylediği için onu tercih edebilirsin. Ortak ata fikrine merge yazısında tekrar döneceğiz, çünkü three-way merge tam olarak bu commit'in üzerine kurulur.

Sık Düşülen Tuzaklar

Birinci tuzak, yeni dosyanın diff'te görünmemesi. Git'in henüz takip etmediği, yani untracked bir dosya git diff çıktısına hiç girmez:

$ git status -s
?? src/orders.js
$ git diff --stat
$ git add -N src/orders.js
$ git diff
diff --git a/src/orders.js b/src/orders.js
new file mode 100644
index 0000000..1daf29c
--- /dev/null
+++ b/src/orders.js
@@ -0,0 +1,3 @@
+const orders = [];
+
+module.exports = { orders };

git add -N (intent-to-add), dosyanın içeriğini stage'lemeden yalnızca "bu dosyayı takip edeceğim" diye not düşer. Böylece yeni dosya da diff'te görünür.

İkincisi, git add'den sonra boş gelen diff'i "değişikliklerim kayboldu" sanmak. Kaybolmadılar, index'e geçtiler; onları git diff --staged gösterir.

Üçüncüsü, iki nokta ile üç noktanın git log ve git diff'te farklı anlamlara gelmesi. git log main..discount "discount'ta olup main'de olmayan commit'ler" demektir ve bu yazıdaki örnekte yalnızca 86303f4'ü listeler. git diff main..discount ise commit listesiyle ilgilenmez, iki ucun içeriğini karşılaştırır. Pull request'in ne getirdiğini merak ediyorsan diff'te üç nokta kullan.

Dördüncüsü, sondaki boşluklar. Editörün göstermediği gereksiz boşluklar diff'i kirletir. src/orders.js'in ilk satırının sonunda iki boşluk unuttuğunu düşün. git diff --check bunları satır numarasıyla yakalar ve sorun bulursa sıfırdan farklı bir çıkış koduyla döner; bu da onu bir pre-commit hook'unda ya da CI adımında kullanılabilir yapar:

$ git diff --check
src/orders.js:1: trailing whitespace.
+const orders = [];  

Küçük Bir Deneme

★☆☆ src/cart.js dosyasında bir satırı değiştir, stage'le, sonra başka bir satırı daha değiştir. git diff, git diff --staged ve git diff HEAD çıktılarındaki satır sayılarını çalıştırmadan önce tahmin et, sonra kontrol et.

★★☆ git diff HEAD~3 HEAD --stat ile git log --stat -3 çıktılarını karşılaştır. Toplam satır sayıları neden tutmayabilir? İpucu: biri iki ucu, diğeri her commit'i ayrı ayrı gösterir.

★★★ main üzerinde, discount branch'inin de değiştirdiği src/cart.js dosyasında küçük bir değişiklik commit'le. Sonra git diff main..discount ve git diff main...discount çıktılarını karşılaştır ve her satırın hangi branch'ten geldiğini açıkla.

Bu yazıda discount branch'ini kısaca kullandık ama altında ne olduğunu konuşmadık. Bir sonraki yazıda branch'in aslında bir commit'i gösteren hareketli bir pointer olduğunu, HEAD'in ne işe yaradığını, git switch ile branch'ler arasında nasıl dolaşıldığını ve detached HEAD durumundan nasıl çıkılacağını göreceğiz.