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 master 'ı 9e5e6a4 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