git reset ve git revert: commit'i nasıl geri alırsınız, geçmişi bozmadan
git reset branch işaretçisini başka bir commit'e taşır ve geçmişi yeniden yazar; bu yüzden yalnızca henüz gönderilmemiş yerel commit'lerde uygundur. git revert ise ters değişiklikleri içeren yeni bir commit oluşturur ve geçmişe dokunmaz; sunucuda zaten olan bir commit'i geri almanın güvenli yoludur.
Kısaca: git reset branch'i geriye sarar ve sanki fazla commit yokmuş gibi davranır. git revert geçmişi olduğu gibi bırakır ve eski commit'in değişikliklerini geri alan yeni bir commit ekler. Birincisi yerel çalışma için uygundur, ikincisi ortak depoya girmiş her şey için. Yalnızca bu kuralı akılda tutarsanız Git'te yaşanan kazaların çoğundan kaçınırsınız.
Aşağıda iki komutun nasıl çalıştığını, reset modlarının farklarını, push edilmiş bir commit'in nasıl geri alınacağını, «kaybolmuş» commit'lerin nasıl geri getirileceğini ve git clean'in rolünü inceleyeceğiz.
Geri almayı değiştiren üç seviye
Farkı anlamak için Git'in üç alanını hatırlayın: çalışma dizini (diskteki dosyalar), index (staging area; bir sonraki commit'e girecekler) ve geçmiş (branch'in ve HEAD'in gösterdiği commit'ler). Her geri alma komutu bu alanların kendine özgü bir kombinasyonunu etkiler.
| Komut | Geçmiş | Index | Çalışma dizini | Ortak branch için güvenli mi |
|---|---|---|---|---|
git restore dosya | hayır | hayır | evet | evet |
git restore --staged dosya | hayır | evet | hayır | evet |
git reset --soft | evet | hayır | hayır | hayır |
git reset --mixed (varsayılan) | evet | evet | hayır | hayır |
git reset --hard | evet | evet | evet | hayır |
git revert | yeni commit ekler | — | — | evet |
git clean | hayır | hayır | izlenmeyen dosyaları siler | evet, ama geri alınamaz |
git reset nasıl çalışır
git reset <commit>, mevcut branch'in işaretçisini (ve onunla birlikte HEAD'i) belirtilen commit'e taşır. Sonradan gelen commit'ler fiziksel olarak silinmez, ama artık branch'te görünmezler. Modu, index'e ve dosyalara ne olacağını belirler.
git reset --soft: commit'i geri al, değişiklikler index'te kalsın
Branch geriye gider; index ve dosyalar olduğu gibi kalır. Geri alınan commit'lerin tüm değişiklikleri commit için hazırlanmış olur. Bir mesajı yeniden yazmanın ya da son birkaç commit'i tek commit'e indirmenin ideal yoludur.
git reset --soft HEAD~3
git commit -m "feat: kayıt formu"
Son üç commit tek bir commit oldu; dosyaların içeriği değişmedi.
git reset --mixed: commit'i ve index'i geri al
Varsayılan moddur: git reset HEAD~1, git reset --mixed HEAD~1 ile aynıdır. Branch geriye gider, index sıfırlanır, değişiklikler çalışma dizininde hazırlanmamış olarak kalır. Fazla commit yaptıysanız ve neyi hangi parçalarla commit edeceğinize yeniden karar vermek istiyorsanız kullanışlıdır.
git reset HEAD~1
git status
# Changes not staged for commit:
# modified: app.py
# modified: config.yml
git reset --hard: tamamen geri dön
Branch, index ve çalışma dizini belirtilen commit'in durumuna getirilir. İzlenen dosyalardaki commit edilmemiş tüm değişiklikler uyarı olmadan kaybolur. reset'in gerçekten işinizi yok edebilen tek modu budur.
git reset --hard HEAD~1 # son commit'i ve tüm düzenlemeleri at
git reset --hard origin/main # branch'i sunucudaki haline getir
--hard'dan önce değerli bir şey varsa git status ve git stash çalıştırın. Commit edilmiş şeyleri geri getirebilirsiniz (aşağıda anlatılıyor), commit edilmemiş olanları getiremezsiniz.
Tek dosya için git reset
Yol belirterek kullanıldığında reset branch'i taşımaz, yalnızca index'i değiştirir: git reset app.py dosyayı hazırlananlardan çıkarır. Günümüzdeki eşdeğeri git restore --staged app.py'dir ve daha açıklayıcıdır. Yol ile birlikte --soft ve --hard bayrakları çalışmaz.
git revert nasıl çalışır
git revert <commit>, belirtilen commit'in değişikliklerini hesaplar, bunları ters yönde uygular ve yeni bir commit oluşturur. Eklenen satırlar silinir, silinenler geri gelir. Geçmiş ileriye doğru büyür, hiçbir şey yeniden yazılmaz; bu yüzden meslektaşlarınızın kendi tarafında bir şey düzeltmesi gerekmez.
git log --oneline
# a3f9c21 (HEAD -> main) docs: README
# 7be41d0 feat: yanıtları önbelleğe al
# 1c02e8f fix: istek zaman aşımı
git revert 7be41d0
# [main 5d8e2aa] Revert "feat: yanıtları önbelleğe al"
# 2 files changed, 3 insertions(+), 41 deletions(-)
Git, Revert "..." biçiminde bir mesajla ve This reverts commit 7be41d0... satırıyla bir editör açar. İyi bir alışkanlık, değişikliğin neden geri alındığını da yazmaktır.
Kullanışlı revert bayrakları
--no-edit— varsayılan mesajı editör açmadan kabul eder.-n(--no-commit) — ters değişiklikleri index'e uygular, commit oluşturmaz. Böylece birkaç commit'i tek bir commit'te geri alabilirsiniz.git revert A..B— bir aralığı geri alır (her commit için ayrı bir revert commit'i, yeniden eskiye doğru).-m 1— merge commit'i için zorunludur: hangi ebeveynin ana çizgi sayılacağını belirtir.
git revert --no-commit HEAD~3..HEAD
git commit -m "Revert: deneysel değişiklik içeren üç commit geri alındı"
Merge commit'i geri alma
Birleştirmenin iki ebeveyni vardır ve Git, «ters» değişikliklerin hangisine göre hesaplanacağını bilemez. Bu yüzden -m olmadan is a merge but no -m option was given hatası alırsınız. Genellikle ana çizgi birinci ebeveyndir:
git revert -m 1 9f1e2ab
Tuzak şudur: daha sonra aynı branch'i yeniden birleştirmek isterseniz Git onun commit'lerini zaten birleşmiş sayar ve yeni değişiklikleri getirmez. Çözüm, önce revert commit'inin kendisini geri almak, sonra yeni düzeltmeleri eklemektir.
git reset mi, git revert mi: hangisi
| Ölçüt | git reset | git revert |
|---|---|---|
| Geçmişle ne yapar | yeniden yazar (commit'ler branch'ten kaybolur) | yeni commit ekler |
| Commit zaten gönderilmiş | push --force gerekir, meslektaşların işini bozar | sıradan git push |
| Geçmişin ortasındaki bir commit'i geri alma | yalnızca sonrakilerle birlikte | tek tek hedeflenebilir |
| Geçmişte iz | kalmaz | kalır, kimin ne zaman geri aldığı görünür |
| Tipik senaryo | push öncesi yerel commit'leri düzenlemek | main'deki bozuk bir commit'i geri almak |
Basit kural: commit'i sizden başka biri gördüyse revert, görmediyse reset. İstisna, kimsenin almadığı kişisel feature branch'iniz: orada geçmişi yeniden yazmak ve git push --force-with-lease yapmak kabul edilebilir.
Ortak branch'te reset --hard neden kötü bir fikir
Diyelim main'de git reset --hard HEAD~2 ve ardından git push --force yaptınız. Meslektaşlarınızda bu iki commit hâlâ duruyor. Bir sonraki git pull'da Git onların dalını sizinkiyle birleştirmeye çalışır; «silinen» commit'ler geri gelir, bu kez de gereksiz bir merge commit'iyle birlikte. Daha kötüsü, biri siz geri aldıktan sonra kendi commit'lerini üstüne gönderdiyse, force push onları tamamen siler.
Bu yüzden korumalı branch'lerde (main, release) force push genellikle depo ayarlarından yasaklanır. Yeniden yazmadan kurtulamıyorsanız git push --force-with-lease kullanın: sunucuda sizin görmediğiniz commit'ler varsa reddeder.
Tipik geri alma senaryoları
Gönderilmemiş son commit'i geri alma
git reset --soft HEAD~1 # değişiklikler index'te, yeniden commit edilebilir
git reset HEAD~1 # değişiklikler dosyalarda, index boş
git reset --hard HEAD~1 # değişiklikler atıldı
Yalnızca mesajı düzeltmek ya da unutulan bir dosyayı eklemek istiyorsanız reset gerekmez: git add unutulan.txt ve git commit --amend yeter.
Push edilmiş commit'i geri alma
git revert <hash>
git push
Bu, sık sorulan «git reverse commit» sorusunun yanıtıdır: Git'te git reverse diye bir komut yoktur; ters commit'i oluşturan komut git revert'tir.
Branch'i sunucudaki haline döndürme
git fetch origin
git reset --hard origin/main
Yerel branch'iniz başarısız bir rebase ya da denemelerden sonra karıştığında işe yarar. Sunucuda olmayan tüm yerel commit'ler branch'ten kaybolur.
Kötü bir reset'ten sonra commit'leri geri getirme
Git, HEAD'in her hareketini reflog günlüğüne yazar. Reset'ten sonra commit'ler nesne veritabanında en az birkaç hafta kalır (varsayılan olarak erişilemeyen reflog kayıtları 30 gün tutulur).
git reflog
# 1c02e8f HEAD@{0}: reset: moving to HEAD~2
# a3f9c21 HEAD@{1}: commit: docs: README
# 7be41d0 HEAD@{2}: commit: feat: yanıtları önbelleğe al
git reset --hard a3f9c21 # ya da HEAD@{1}
Reflog yereldir: yalnızca sizin klonunuzda bulunur ve sunucuya gönderilmez.
git clean: izlenmeyen dosyaları silmek
git reset --hard, Git'in izlemediği dosyalara dokunmaz: index'e hiç eklenmemiş yeni dosyalar, derleme çıktıları, günlükler. Bunlar için git clean vardır. Komut geri alınamaz: sildiği dosyalar ne reflog'a ne de çöp kutusuna düşer.
git clean -n # kuru çalıştırma: neyin silineceğini göster
git clean -f # izlenmeyen dosyaları sil
git clean -fd # bunlara izlenmeyen klasörleri de ekle
git clean -fdx # ayrıca yok sayılan dosyalar (.gitignore): node_modules, dist
git clean -i # etkileşimli seçim
-f olmadan Git varsayılan olarak çalışmayı reddeder (clean.requireForce ayarı). Her zaman -n ile başlayın; özellikle -x ile, çünkü yok sayılan yerel .env dosyanızı da silebilir.
Son commit'e tam sıfırlama için bağlantılı komut: git reset --hard && git clean -fd.
Sık yapılan hatalar
- --soft yerine --hard kullanmak. Commit edilmiş işi reflog kurtarır, ama commit edilmemiş olan kaybolur. Deneme yapmadan önce
git stashalmak bu sorunu önler. - Bağlamını bilmeden revert yapmak. Daha sonraki commit'ler aynı satırları değiştirdiyse revert çakışma verir. Normal bir çakışma gibi çözün: dosyayı düzeltin,
git add,git revert --continue; vazgeçtiysenizgit revert --abort. - restore ile reset'i karıştırmak. Tek bir dosyadaki değişiklikleri geri almak için tüm branch'i reset etmenize gerek yok:
git restore dosya. - main'e force push. Geçmiş biraz uzasa bile revert ve sıradan push daha iyidir.
Kısaca
git reset, kendi henüz yayımlanmamış geçmişiniz için bir araçtır: --soft değişiklikleri index'te tutar, --mixed dosyalarda, --hard her şeyi atar. git revert ise ortak branch'lerde güvenli geri almadır ve geçmişte dürüst bir iz bırakır. git clean izlenmeyen dosyaları temizler, ama geri dönüşü yoktur. Restore, reset ve revert ile geri alma senaryoları Git kursunda uygulamalı olarak ele alınmaktadır.
Sık sorulan sorular
git reset commit'leri kalıcı olarak siler mi?
Hayır. Commit'ler nesne veritabanında kalır ve git reflog üzerinden erişilebilir; çöp toplayıcı onları silene kadar — erişilemeyen kayıtlar için varsayılan olarak 30 gün — bu geçerlidir.
git revert'i geri alabilir miyim?
Evet. Revert commit'i sıradan bir commit'tir; başka bir git revert ile ya da henüz gönderilmediyse git reset ile kaldırabilirsiniz.
git reset ile git checkout arasındaki fark nedir?
git reset mevcut branch'in işaretçisini taşır; git checkout (ya da git switch) ise HEAD'i başka bir branch'e ya da commit'e çevirir ve branch'in kendisini değiştirmez.
HEAD~1 ile HEAD^ farkı nedir?
İkisi de mevcut commit'in ebeveynini gösterir; HEAD~3 ilk ebeveynler üzerinden üçüncü atadır, HEAD^2 ise bir merge commit'inin ikinci ebeveynidir.
git clean işlemini nasıl geri alırım?
Git araçlarıyla geri alamazsınız: silinen izlenmeyen dosyalar depoda saklanmamıştı. Bu yüzden her zaman önce git clean -n çalıştırın.
Bu konu “Sıfırdan Güvenilir Geliştiriciye Git” kursunda uygulamalı olarak ayrıntılı işleniyor.
Kursa git