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

Git ile Değişiklikleri Geri Almak 1: git restore ve git reset

Mert Özen 6 Kas 2026 8 dk 1 görüntülenme
Git ile Değişiklikleri Geri Almak 1: git restore ve git reset

git restore ile dosyaları working tree'de ve index'te geri almak, --source ile geçmişten getirmek; git reset'in --soft, --mixed ve --hard modlarının HEAD'e, index'e ve working tree'ye ne yaptığı, ORIG_HEAD ile geri dönmek ve eski checkout/reset biçimleri.

Sürüm notu: Bu yazıdaki komutlar Git 2.54 ile denenmiştir; git restore Git 2.23'ten beri var ve Git 2.51'den beri experimental değil.

Önce Tahmin Et

Ada sepete indirim parametresini ekledi ve değişikliği stage'ledi. Sonra hata ayıklamak için bir console.log satırı ekledi ama onu stage'lemedi:

$ git add src/cart.js
$ git status -s
MM src/cart.js
$ git diff
diff --git a/src/cart.js b/src/cart.js
index 3f2d943..cf90641 100644
--- a/src/cart.js
+++ b/src/cart.js
@@ -1,5 +1,6 @@
 function total(items, discount = 0) {
   const sum = items.reduce((acc, item) => acc + item.price, 0);
+  console.log("total", sum, discount);
   return sum - discount;
 }

MM dosyanın iki kez değiştiğini söylüyor: soldaki harf index'teki, sağdaki working tree'deki değişiklik. Ada şimdi git restore src/cart.js çalıştırıyor. Dosya hangi hâline döner: son commit'teki indirimsiz hâline mi, yoksa stage'lediği indirimli hâline mi?

$ git restore src/cart.js
$ git status -s
M  src/cart.js
$ git diff --staged
diff --git a/src/cart.js b/src/cart.js
index 4876df7..3f2d943 100644
--- a/src/cart.js
+++ b/src/cart.js
@@ -1,5 +1,6 @@
-function total(items) {
-  return items.reduce((sum, item) => sum + item.price, 0);
+function total(items, discount = 0) {
+  const sum = items.reduce((acc, item) => acc + item.price, 0);
+  return sum - discount;
 }

 module.exports = { total };

İndirimli hâline. console.log gitti, indirim stage'de duruyor. git restore varsayılan olarak dosyayı son commit'ten değil index'ten geri getirir. Bu yazının bütün konusu bu tür sorular: hangi komut hangi alana dokunur?

Üç Alan, İki Komut

Üçüncü yazıda gördüğümüz üç alanı hatırla. HEAD, bulunduğun branch'in gösterdiği son commit. Index (staging area), bir sonraki commit'in taslağı. Working tree, diskte düzenlediğin dosyalar. Bir değişikliği geri almak, bu alanlardan birini ya da birkaçını başka bir alanın içeriğine geri çekmek demek.

Bu yazıdaki iki komut farklı ölçeklerde çalışır. git restore dosya düzeyinde çalışır: belirli dosyaları index'te ya da working tree'de eski hâline getirir, branch'e ve commit'lere dokunmaz. git reset ise commit düzeyinde çalışır: bulunduğun branch'i başka bir commit'e taşır, moduna göre index'i ve working tree'yi de peşinden sürükler. Bir kitabın taslağı gibi düşün: restore tek bir sayfadaki silgi, reset ise "şu bölümden sonrasını yok say" demek.

git restore: Working Tree'yi Geri Almak

Önce Tahmin Et'te gördüğün gibi, yalnızca dosya adıyla çalışan git restore, working tree'deki dosyayı index'teki hâline getirir. Stage'lenmemiş değişiklikler kaybolur, stage'lenmiş olanlar yerinde kalır. Bu yüzden stage'i bir kayıt noktası gibi kullanabilirsin: işe yarayan bir adımı stage'le, denemeye devam et, deneme kötü giderse git restore ile o noktaya dön.

Bir uyarı: bu komutun geri dönüşü yok. Stage'lenmemiş değişiklik hiçbir zaman Git'in object veritabanına girmedi; reflog da onu kurtaramaz. git restore . ise bunu klasördeki bütün dosyalar için tek seferde yapar. Çalıştırmadan önce git diff ile neyi kaybedeceğine bak.

--staged: Stage'den Çıkarmak

İndirim değişikliğini stage'den çıkarmak ama dosyada tutmak istersen --staged kullanırsın. Bu kez index HEAD'e döner, working tree'ye dokunulmaz:

$ git restore --staged src/cart.js
$ git status -s
 M src/cart.js
$ git restore src/cart.js
$ git status -s

M ile M arasındaki fark harfin hangi sütunda olduğu: değişiklik index'ten çıktı ve yalnızca working tree'de kaldı. Ardından gelen git restore onu da attı ve status boş döndü. İkisini tek seferde yapmak için iki seçeneği birlikte verirsin. Dosyayı bozup stage'lediğini düşün:

$ git add src/cart.js
$ git status -s
M  src/cart.js
$ git restore --staged --worktree src/cart.js
$ git status -s

--staged ile --worktree birlikte verildiğinde kaynak HEAD olur; Git 2.27'den beri böyle. Eski yazılarda ve StackOverflow cevaplarında aynı işler için başka komutlar göreceksin: stage'den çıkarmak için git reset HEAD src/cart.js, working tree'yi geri almak için git checkout -- src/cart.js. İkisi de hâlâ çalışır ve checkout Git 3.0'da da kalkmayacak. Ama checkout hem branch değiştirdiği hem dosya ezdiği için yanlış argümanla çağrıldığında sürpriz yapar; restore tek bir iş yapar.

--source: Dosyayı Geçmişten Getirmek

Bir dosyanın birkaç commit önceki hâline ihtiyacın varsa --source ile kaynağı sen seçersin. Bu arada iki commit daha atılmış olsun:

$ git log --oneline
22e4005 Add Ulysses
3a58559 Raise Dune price
7f7eeec Add cart total
61419c1 Add book list
5102cb8 Add README and .gitignore
$ git restore --source=HEAD~2 src/books.js
$ git diff
diff --git a/src/books.js b/src/books.js
index 345c437..aaa1ad1 100644
--- a/src/books.js
+++ b/src/books.js
@@ -1,7 +1,6 @@
 const books = [
-  { id: 1, title: "Dune", price: 14 },
+  { id: 1, title: "Dune", price: 12 },
   { id: 2, title: "Emma", price: 9 },
-  { id: 3, title: "Ulysses", price: 15 },
 ];

 module.exports = { books };
$ git restore src/books.js

HEAD~2, HEAD'den iki commit geri, yani 7f7eeec. Dosyanın o andaki hâli working tree'ye geldi; commit'ler ve branch yerinde duruyor, git diff bunu sıradan bir değişiklik olarak gösteriyor. İstersen commit'lersin, istemezsen son komuttaki gibi geri alırsın. Kaynak olarak bir branch adı, tag ya da hash de verebilirsin.

git reset --soft: Yalnızca Branch'i Taşımak

Şimdi commit düzeyine geçelim. Son iki commit aslında tek bir iş: kitap listesini güncellemek. Onları tek commit'e indirmek istiyorsun:

$ git reset --soft HEAD~2
$ git status -s
M  src/books.js
$ git log --oneline -1
7f7eeec Add cart total
$ git commit -m "Update book list"
[main b0a86e1] Update book list
 1 file changed, 2 insertions(+), 1 deletion(-)
$ git log --oneline
b0a86e1 Update book list
7f7eeec Add cart total
61419c1 Add book list
5102cb8 Add README and .gitignore

--soft yalnızca branch'i taşıdı: main artık 7f7eeec'i gösteriyor. Index ve working tree olduğu gibi kaldı, bu yüzden iki commit'in toplam değişikliği stage'lenmiş hâlde bekliyordu. Tek bir commit ile ikisini birleştirdin. Son commit'in mesajını düzeltmek için commit --amend yeterli; --soft birden çok commit'i birleştirmek ya da yeniden düzenlemek istediğinde işe yarar.

git reset --mixed: Commit'i ve Stage'i Geri Almak

--mixed varsayılan moddur; mod yazmazsan bu çalışır. Branch'i taşır ve index'i de o commit'e eşitler, working tree'ye dokunmaz. Ada README'deki bir notu commit'lerken -a yüzünden debug satırını da commit'e kattı:

$ git commit -am "Document how to run the cart"
[main ea4bddd] Document how to run the cart
 2 files changed, 3 insertions(+)
$ git reset HEAD~1
Unstaged changes after reset:
M	README.md
M	src/cart.js
$ git status -s
 M README.md
 M src/cart.js
$ git add README.md && git commit -m "Document how to run the cart"
[main 3213cc1] Document how to run the cart
 1 file changed, 2 insertions(+)
$ git status -s
 M src/cart.js

Commit geri alındı, değişiklikler stage'den çıktı ama dosyalarda duruyor. Ada yalnızca README'yi seçip yeniden commit'ledi, debug satırı working tree'de kaldı. "Commit'i geri al, ne stage'leyeceğime yeniden karar vereyim" dediğin durumların modu bu.

git reset --hard: Her Şeyi Geri Almak

--hard üç alanı da hedef commit'e eşitler: branch taşınır, index ve working tree o commit'in içeriğine döner. Commit vermeden çalıştırırsan hedef HEAD olur, yani yalnızca commit'lenmemiş değişiklikler atılır:

$ git reset --hard
HEAD is now at 3213cc1 Document how to run the cart
$ git status -s
$ git reset --hard HEAD~1
HEAD is now at b0a86e1 Update book list
$ git log --oneline -2
b0a86e1 Update book list
7f7eeec Add cart total
$ git reset --hard ORIG_HEAD
HEAD is now at 3213cc1 Document how to run the cart
$ git log --oneline -2
3213cc1 Document how to run the cart
b0a86e1 Update book list

İlk komut debug satırını sessizce sildi. İkincisi son commit'i de attı. Üçüncüsü onu geri getirdi: ORIG_HEAD; reset, merge ve rebase gibi HEAD'i büyük adımlarla taşıyan komutların, taşımadan önceki konumu yazdığı bir referans. Commit'ler hemen silinmez, bir süre erişilebilir kalır; nasıl bulunacaklarını reflog yazısında göreceğiz. Ama ilk komutta atılan commit'lenmemiş satırın böyle bir dönüş yolu yok. Daha temkinli bir alternatif --keep: branch'i taşır ama commit'lenmemiş değişikliği ezecekse durur.

Sık Düşülen Tuzaklar

İlk tuzak push edilmiş commit'leri reset'lemek. Yerelde geçmişi geri aldığında remote hâlâ eski commit'leri gösterir; bir sonraki push reddedilir ve geçmek için force push gerekir, bu da ekip arkadaşlarının geçmişini bozar. Paylaşılmış bir commit'i geri almanın güvenli yolu git revert; bir sonraki yazının konusu.

İkincisi --hard'ı alışkanlıkla yazmak. Çoğu zaman istediğin şey --soft ya da --mixed; değişiklikler yerinde kalır ve fikrini değiştirebilirsin. --hard öncesi git status ve git diff'e bakmayı refleks hâline getir.

Üçüncüsü reset --hard'ın her şeyi temizlediğini sanmak. Untracked dosyalara dokunmaz; onlar için git clean var ve o da geri dönüşsüzdür, önce git clean -n ile neyin silineceğine bak.

Dördüncüsü restore'un varsayılan kaynağını unutmak. Yalnızca dosya adıyla git restore dosyayı HEAD'den değil index'ten getirir; stage'lenmiş bir değişikliği de atmak istiyorsan --staged --worktree gerekir.

Küçük Bir Deneme

★☆☆ Bir dosyada iki ayrı değişiklik yap, birini stage'le. git restore, git restore --staged ve git restore --staged --worktree komutlarını sırayla dene ve her birinden sonra git status -s çıktısındaki iki sütunu oku.

★★☆ Üç commit at, sonra git reset --soft HEAD~3 ile hepsini tek commit'e indir. Aynısını --mixed ile yap; bu kez yeniden commit'lemeden önce ne yapman gerekiyor?

★★★ Sırayla git reset --hard HEAD~2, git reset --soft HEAD~1 ve git reset --hard ORIG_HEAD çalıştır. Çalıştırmadan önce tahmin et: en baştaki commit'e mi döneceksin? git log --oneline -3 ile kontrol et ve sonucu ORIG_HEAD'in ne zaman yazıldığıyla açıkla.

Yerelde geri almanın yollarını gördük. Bir sonraki yazıda paylaşılmış geçmişte geri almaya geçiyoruz: git revert ile geçmişi silmeden ters bir commit eklemek, reset ile revert arasındaki seçim, bir merge commit'i -m 1 ile revert etmek ve --no-commit ile birden çok commit'i tek seferde geri almak.