Git
Chapters ▾ 2nd Edition

7.7 Git Araçları - Reset Komutunun Gizemleri

Reset Komutunun Gizemleri

Daha özelleşmiş araçlara geçmeden önce, Git’in reset ve checkout komutlarından bahsedelim. Bu komutlar, ilk kez karşılaştığınızda Git’in en karmaşık kısımlarından ikisidir. Bu kadar çok şey yaparlar ki, onları gerçekten anlamak ve doğru bir şekilde kullanmak umutsuz görünür. Bu nedenle, basit bir metafor öneriyoruz.

Üç Çalışma Ağacı

Git’in üç farklı çalışma ağacının içeriğini yöneten bir içerik yöneticisi olduğunu hayal etmek, reset ve checkout komutlarını anlamayı kolaylaştırır. Burada "ağaç" derken gerçekten "dosya dizinini" kastediyoruz, bir veri yapısı olan ağacı (tree) değil. (Birkaç durumda, dizin tam olarak bir ağaç gibi davranmaz, ancak şu anda amacımız için bu şekilde düşünmek daha kolaydır.)

Git sistemi normal işleyişinde üç ağacı yönetir ve onları değiştirir:

Ağaç (Tree) Rol

Uç (HEAD)

Son katkı pozu, ardıl

Dizin (Index)

Önerilen katkı pozu (bir sonraki işlem için)

Çalışma Dizini

Kum havuzu (Sandbox)

Uç (HEAD)

Uç, mevcut dalda işlenen son katkının referansını gösteren bir işaretçidir. Bunun anlamı, bu "uç"un işlenen bir sonraki katkının önceli olacağıdır. Genellikle "uç"u projenizin o daldaki son katkınızı işlediğiniz andaki anlık görüntüsü (poz) olarak düşünmek en basit olanıdır.

Aslında, bu pozun nasıl olduğunu görmek oldukça kolaydır. İşte, uç pozundaki her dosyanın gerçek dizin listesi ve SHA-1 kontrol toplamlarını almanın bir örneği:

$ git cat-file -p HEAD
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
author Scott Chacon  1301511835 -0700
committer Scott Chacon  1301511835 -0700

initial commit

$ git ls-tree -r HEAD
100644 blob a906cb2a4a904a152...   README
100644 blob 8f94139338f9404f2...   Rakefile
040000 tree 99f1a6d12cb4b6f19...   lib

Git’in düşük seviyeli işlerde kullanılan cat-file ve ls-tree komutları, günlük işlerde pek kullanılmayan; ancak burada neler olup bittiğini görmemize yardımcı olan, “plumbing” (boru) komutlarıdır.

İndeks (Dizin)

İndeks, beklenilen sıradaki katkı işlemidir. Bu kavramı aynı zamanda Git’in ``İzlem Alanı`` (Staging Area) olarak da adlandırıyoruz, çünkü git commit komutunu çalıştırdığınızda Git’in baktığı yer burasıdır.

Git, bu indeksi, çalışma dizininize en son eklenen tüm dosya içeriği listesiyle doldurur ve eklendikleri anda onların aslında neye benzediğine bakar. Daha sonra, siz bu dosyalardan bazılarını yeniden şekillendirirsiniz ve git commit komutu bunu yeni bir katkı için ağaca dönüştürür.

$ git ls-files -s
100644 a906cb2a4a904a152e80877d4088654daad0c859 0	README
100644 8f94139338f9404f26296befa88755fc2598c289 0	Rakefile
100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0	lib/simplegit.rb

Burada yine, aslında bize dosya dizinimizin şu anda neye benzediğini gösterecek bir perde-gerisi aracı olan, git ls-files komutunu kullanıyoruz.

İndeks teknik olarak bir ağaç yapısı değildir - aslında düzleştirilmiş bir dışavurum olarak uygulanmıştır - ancak amacımız için buna yeterince yakındır.

Çalışma Dizini

Son olarak, çalışma dizininiz (ayrıca "çalışma ağacı" olarak da adlandırılır). Diğer iki ağaç, içeriklerini etkili ancak kullanışsız bir şekilde .git klasörü içinde saklar. Çalışma dizini, bunları gerçek dosyalara açar, bu da onları düzenlemenizi çok daha kolay hale getirir. Çalışma dizinini, değişikliklerinizi (izleme alanına alıp ardından katkı olarak proje geçmişinize eklemeden önce) deneyebileceğiniz bir kum havuzu (sandbox) olarak düşünün.

$ tree
.
├── README
├── Rakefile
└── lib
    └── simplegit.rb

1 directory, 3 files

İş Akışı

Git’in tipik iş akışı, bu üç ağacı manipüle ederek projenizin ardışık olarak daha gelişmiş durumlarının pozlarını kaydetmektir.

reset workflow

Hadi bu süreci görselleştirelim: Diyelim ki tek bir dosyanın bulunduğu yeni bir dizine giriyorsunuz. Mavi renkte göstereceğimiz bu dosyayı v1 olarak adlandıralım. Şimdi git init komutunu çalıştırıyoruz: bu daha doğmamış master dalına işaret eden bir HEAD referansı ile bir Git reposu oluşturacak.

reset ex1

Bu noktada, sadece çalışma dizini ağacında içerik bulunmaktadır.

Şimdi bu dosyayı katkı olarak işlemek istiyoruz, bu yüzden git add komutunu kullanarak çalışma dizinindeki içeriği alıp indekse (izlem) kopyalarız.

reset ex2

Ardından git commit komutunu çalıştırırız: bu komut dizinin içeriğini alır ve onu kalıcı bir poz olarak kaydeder, o poza işaret eden bir katkı nesnesi oluşturur ve master 'ı bu katkıya işaret edecek şekilde günceller.

reset ex3

git status komutunu çalıştırırsak, henüz değişiklik yapmadığımız için, her üç ağacın da aynı olduğunu göreceğiz.

Şimdi dosyayı değiştirip, katkı olarak işlemek istiyoruz. Aynı süreci geçireceğiz; önce çalışma dizininde dosyayı değiştireceğiz. Dosyanın bu sürümüne v2 diyelim ve onu kırmızı renkle gösterelim.

reset ex4

Şu anda git status komutunu çalıştırırsak, dosyayı kırmızı renkte ve ``Changes not staged for commit`` açıklamasıyla göreceğiz; çünkü bu giriş indeks ile çalışma dizini arasındaki bir farklılık olarak görünecektir. Daha sonra bu dosya üzerinde git add komutunu çalıştırarak, onu indekse ekliyoruz.

reset ex5

Bu noktada, git status komutunu çalıştırırsak, dosyayı yeşil renkte "Yapılacak katkılar" altında göreceğiz; çünkü indeks ile uç farklıdır - yani önerilen sıradaki katkımız, işlenmiş son katkımızdan farklıdır. Son olarak, katkılama işlemini tamamlamak için git commit komutunu çalıştırıyoruz.

reset ex6

Şimdi git status komutunu çalıştırdığımızda, tekrar tüm ağaçlar aynı olduğu için herhangi bir çıktı almayacağız.

Dallar arasında geçiş yapmak veya kopyalamak benzer bir süreçten geçer. Yeni bir dala geçiş yapmak, HEAD işaretçisini yeni dal referansını gösterecek şekilde değiştirir, indeks içeriğini o katkının pozuyla doldurur ve son olarak indeks içeriğini çalışma dizinine kopyalar.

Reset Komutunun Rolü

reset komutu, bu bağlamda görüldüğünde daha mantıklı hale gelir.

Bu örnekleri daha iyi anlamak için, diyelim ki file.txt dosyasını tekrar değiştirdik ve üçüncü kez katkıladık. Şimdi geçmişimiz şöyle görünüyor:

reset start

Şimdi, reset çağrıldığında tam olarak ne yaptığını adım adım görelim. Basit ve öngörülebilir bir şekilde üç ağacı doğrudan manipüle eder. Üç temel işlem yapar.

Adım 1: HEAD’i Taşı

İlk olarak, reset 'in yapacağı şey, HEAD’in işaret ettiği yeri taşımaktır. Bu, HEAD’in kendisini değiştirmekle aynı değildir (bunu checkout yapar); reset, HEAD’in işaret ettiği dalı taşır. Bu, eğer HEAD master dalına ayarlanmışsa (yani şu anda master dalındaysanız), git reset 9e5e6a4 komutunu çalıştırdığınızda master9e5e6a4 noktasına getirecek demektir.

reset soft

Bir katkı ile reset 'in hangi biçimini çağırırsanız çağırın, bu her zaman yapmaya çalışacağı ilk şeydir. reset --soft komutuyla orada duracaktır.

Şimdi o diyagrama bir kez daha göz atın ve neler olduğunun farkına varın: temel olarak son git commit komutunu geri aldı. git commit komutunu çalıştırdığınızda, Git yeni bir katkı oluşturur ve HEAD’in işaret ettiği dalı oraya taşır. reset komutunu HEAD~ 'e (HEAD’in önceli) geri alırsanız, dalı indeks veya çalışma dizininde herhangi bir değişiklik yapmadan eski konumuna geri taşırsınız. Şimdi indeksi güncelleyebilir ve git commit komutunu tekrar çalıştırarak git commit --amend komutunun yaptığını başarabilirsiniz (bkz Son Katkıyı Değiştirme).

Adım 2: İndeksi Güncelleme (--mixed)

Şimdi git status komutunu çalıştırırsanız, indeks ile yeni HEAD arasındaki farkı yeşil renkte göreceksiniz.

reset 'in yapacağı bir sonraki şey, indeksi, HEAD’in şu anda işaret ettiği pozun içeriğiyle güncellemektir.

reset mixed

--mixed seçeneğini belirtirseniz, reset işlemi bu noktada duracaktır. Bu aynı zamanda varsayılan davranıştır, yani hiçbir seçenek belirtmezseniz (bu durumda yalnızca git reset HEAD~), komut burada duracaktır.

Şimdi o diyagrama bir kez daha bir göz atın ve neler olduğunun farkına varın: hala son commit işleminizi geri aldınız, ancak aynı zamanda her şey izlem alanı dışına çıktı. Yani, tüm git add ve git commit komutlarınızı çalıştırmadan önceki duruma geri döndünüz.

Adım 3: Çalışma Dizinini Güncelleme (--hard)

reset 'in yapacağı üçüncü şey, çalışma dizinini indeks gibi yapmaktır. --hard seçeneğini kullanırsanız, bu aşamaya devam eder.

reset hard

Az önce ne olduğunu bir düşünelim. Son katkınızı, git add ve git commit komutlarını, ve çalışma dizininde yaptığınız tüm çalışmayı geri aldınız.

--hard bayrağının reset komutunu tehlikeli hale getiren tek yol olduğunu ve Git’in bir veriyi gerçekten yok edeceği çok az durumdan biri olduğunu bilmeniz çok önemlidir. reset 'in diğer herhangi bir kullanımı oldukça kolay bir şekilde geri alınabilirken, --hard seçeneği bunu yapamaz; çünkü çalışma dizinindeki dosyaların üzerine zorla yeniden yazar. Bu özel durumda, Git veritabanımızda dosyanın v3 sürümüne bir katkı olarak hala sahibiz ve reflog 'umuza bakarak onu geri alabiliriz; ancak onu katkılamadan bıraksaydık, Git o dosyanın üzerine yeniden yazacaktı ve onu geri alınamaz hale getirecekti.

Özet

reset komutu, belirli bir sırayla bu üç ağacın üzerine yazar ve siz ona durmasını söylediğinizde durur:

  1. HEAD’in işaret ettiği dalı taşı (eğer --soft kullanılmışsa burada dur)

  2. İndeksi HEAD’in aynısı yap (eğer --hard kullanılmamışsa burada dur)

  3. Çalışma dizinini indeks gibi yap

Dosya Dizini ile Sıfırlama

Bu, reset 'in temel formundaki davranışını kapsar, ancak isterseniz bir dizin de belirtebilirsiniz. Bir dizin belirtirseniz, reset adım 1’i atlar ve geri kalan işlemleri belirli bir dosya veya dosya kümesiyle sınırlar. Bu aslında bir bakıma mantıklıdır - HEAD sadece bir işaretçidir ve onu aynı anda bir katkının bir kısmına ve başka bir katkının başka bir kısmına doğrultamazsınız. Ancak indeks ve çalışma dizini kısmen güncellenebilir, bu nedenle reset yoluna 2. ve 3. adımlarla devam eder.

Öyleyse, git reset file.txt komutunu çalıştıralım. Bu form (bir katkı SHA-1 karması, bir dal ya da --soft veya --hard gibi bir bayrak belirtmediğiniz için) git reset --mixed HEAD file.txt söz diziminin kısaltmasıdır ve şunları yapar:

  1. HEAD’in işaret ettiği dalı taşır (atlanır)

  2. İndeksi HEAD’e benzet (burada dur)

Yani temelde file.txt dosyasını HEAD’ten indekse kopyalar.

reset path1

Bu, pratikte dosyanın izlemden çıkarılması etkisine sahiptir. Bu komutun diyagramına bakarsak ve git add komutunun ne yaptığını düşünürsek, tam olarak zıt olduklarını görürüz.

reset path2

Bu nedenle, git status komutunun çıktısı, bir dosyayı izlemden çıkarmak için bunu çalıştırmanızı önerir. (Daha fazla bilgi için: İzleme Alınmış Dosyayı izlemden Çıkarmak)

Git’in "veriyi HEAD’den çek" dediğimizi varsaymasını önlemek için belirli bir katkıyı belirtebiliriz. Yoksa, sadece git reset eb43bf file.txt gibi bir şey çalıştırmak yeterdi.

reset path3

Bununla, etkili bir şekilde (aslında tüm bu adımları geçmeden) dosyanın içeriğini çalışma dizinindeki v1'e geri döndürdük, üzerine git add çalıştırdık, ardından tekrar v3'e geri döndürdük. Şimdi git commit komutunu çalıştırırsak, aslında çalışma dizinimizde hiç sahip olmadığımız halde, dosyayı tekrar v1'e geri döndüren bir değişikliği kaydedecektir.

Ayrıca, aynı git add gibi, reset komutu da içeriği izlemden parça parça çıkarmak için --patch seçeneğini kabul eder. Bu şekilde içeriği seçici olarak izlemden çıkarabilir veya geri alabilirsiniz.

Sıkıştırma (squashing)

Bu yeni keşfedilen güçle ilginç bir şeyin nasıl yapılacağına bakalım: katkıları sıkıştırmak.

Diyelim ki ``oops.``, ``WIP`` ve ``bu dosyayı unuttum`` gibi mesajlar içeren bir dizi katkınız var. Bunları hızlı ve kolay bir şekilde tek bir işleme dönüştürmek ve gerçekten zeki görünmenizi sağlamak için reset komutunu kullanabilirsiniz. Sıkıştırma (Katkıları Sıkıştırmak) bunu yapmanın başka bir yoludur, ancak bu örnekte reset 'i kullanmak daha basittir.

Diyelim ki, ilk katkının bir dosyaya sahip olduğu, ikinci katkının yeni bir dosya ekleyip ilkini değiştirdiği ve üçüncü katkının ilk dosyayı yeniden değiştirdiği bir projeniz var. İkinci katkı devam eden bir çalışmaydı ve siz onu ortadan kaldırmak istiyorsunuz.

reset squash r1

HEAD dalını daha eski bir katkıya (saklamak istediğiniz en son katkıya) geri taşımak için git reset --soft HEAD~2 komutunu çalıştırabilirsiniz:

reset squash r2

Ve sonra tekrar "git commit" komutunu çalıştırın:

reset squash r3

Artık itme yapabileceğiniz geçmişinizin, birinci katkının file-a.txt dosyasının v1 sürümüne sahip olduğunu ve ikinci bir katkının hem file-a.txt 'yi v3 sürümüne değiştirdiğini hem de file-b.txt 'yi eklediğini görebilirsiniz. Dosyanın v2 sürümüyle yapılan kayıt ise artık geçmişte yer almaz.

Check It Out

Son olarak, checkout ve reset arasındaki farkı merak ediyor olabilirsiniz. Reset gibi, checkout da üç ağacı manipüle eder ama komuta bir dosya dizini belirtip belirtmemenize bağlı olarak biraz farklılık gösterir.

Dizinsiz

git checkout [dal] komutunu çalıştırmak, üç ağacı da [dal] gibi görünmesi için güncellemek açısından git reset --hard [dal] komutunu çalıştırmakla benzerdir, ancak arada iki önemli fark vardır.

İlk olarak, reset --hard 'ın aksine, checkout çalışma dizini güvenlidir; değiştirilmiş dosyaları silinmekten korumak için bir kontrol yapılır. Aslında, biraz daha akıllıca davranır — çalışma dizininde basit bir birleştirme yapmaya çalışır, bu nedenle değiştirmemiş olduğunuz tüm dosyalar güncellenecektir. Buna karşın, reset --hard, sorgulamadan her şeyi değiştirir.

İkinci önemli fark, checkout 'un HEAD’i nasıl güncellediğidir. Reset komutu, HEAD’in işaret ettiği dalı taşırken, checkout komutu, HEAD’in kendisini başka bir dala işaret etmek üzere taşır.

Örneğin, farklı katkılara işaret eden master ve develop dallarımız olduğunu ve şu anda develop dalında olduğumuzu varsayalım (yani HEAD buna işaret eder). Eğer git reset master komutunu çalıştırırsak, develop dalı artık master 'ın işaret ettiği aynı katkıya işaret eder. Eğer bunun yerine git checkout master komutunu çalıştırırsak, develop dalı yer değiştirmez, HEAD kendisi hareket eder. HEAD artık master 'ı işaret etmektedir.

Yani, her iki durumda da HEAD’i A katkısına taşıyoruz, ancak bunu yapma şeklimiz çok farklıdır. Reset komutu, HEAD’in işaret ettiği dali taşırken, checkout komutu HEAD’i taşır.

reset checkout

Dizinli

checkout komutunu çalıştırmanın diğer bir yolu, dosya yolunu belirtmek şeklinde olup, bu da reset gibi, HEAD’i taşımaz. Bu, belirli bir dosyayı belirli bir katkıda dizine güncelleyen, ancak aynı zamanda çalışma dizinindeki dosyayı da üzerine yazan git reset [dal] dosya komutu gibi davranır. Eğer reset komutu buna izin verseydi, tam olarak git reset --hard [dal] dosya komutuna benzer olurdu — çalışma dizini güvende olmaz ve HEAD’i taşımaz.

Ayrıca, git reset ve git add gibi, checkout da dosya içeriğini parça parça geri almanıza izin vermek için --patch seçeneğini kabul eder.

Özet

Artık reset komutunu genel olarak anladınız ve daha rahat hissediyorsunuzdur. Ancak yine de tam olarak checkout komutundan nasıl farklı olduğu konusunda kafanızda biraz karışıklık kalmış olabilir. Zaten farklı kullanımların tüm kurallarını ezbere bilmek de imkansızdır.

İşte hangi komutların hangi ağaçları etkilediğine dair bir hatırlatma notu. HEAD sütunu, komutun HEAD’in işaret ettiği referansı (dalı) taşıyıp taşımadığını belirtir; ve eğer HEAD’i taşıyorsa HEAD, aksi takdirde REF okunur. "Çalışma Ağacı Güvende mi?" sütununa özellikle dikkat edin — eğer Hayır yazıyorsa, bu komutu çalıştırmadan önce bir kez daha düşünün.

HEAD Index Workdir WD Safe?

Katkı Seviyesi

reset --soft [commit]

REF

NO

NO

YES

reset [commit]

REF

YES

NO

YES

reset --hard [commit]

REF

YES

YES

NO

checkout <commit>

HEAD

YES

YES

YES

File Level

reset [commit] <paths>

NO

YES

NO

YES

checkout [commit] <paths>

NO

YES

YES

NO

scroll-to-top