Git
Chapters ▾ 2nd Edition

10.2 Dahili Git Ögeleri - Git Nesneleri

Git Nesneleri

Git, içeriğe dayalı bir dosya sistemidir. İyi, güzel de bu ne anlama geliyor? Bu, Git’in temelinde basit bir anahtar-değer veri reposunun bulunduğu anlamına gelir. Bu ise Git reposuna her türlü içeriği ekleyebileceğiniz ve Git’in size daha sonra bu içeriği almak için kullanabileceğiniz benzersiz bir anahtar vereceği anlamına gelir.

Görselleştirmek adına: bir veri alıp, onu .git/objects dizinine (nesne veritabanı) saklayacak ve bu veri nesnesine işaret eden benzersiz anahtarı size geri verecek olan git hash-object tesisat komutuna bir bakalım.

Öncelikle, yeni bir Git reposu başlatın ve (tahmin edilebilir bir şekilde) objects dizininde hiçbir şey olmadığından emin olun:

$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f

Git objects dizinini başlattı ve içinde pack ve info alt dizinlerini oluşturdu, ancak henüz düzenli dosyalar bulunmamaktadır. Şimdi, git hash-object kullanarak yeni bir veri nesnesi oluşturun ve bu nesneyi yeni Git veritabanınıza manuel olarak kaydedin:

$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

En basit haliyle git hash-object, verdiğiniz içeriği alır ve bu içeriği Git veritabanınızda depolamak için kullanılacak benzersiz anahtarı size verir. -w seçeneği komuta yalnızca anahtarı döndürmek yerine bu nesneyi veritabanına yazmasını söyler. Son olarak --stdin seçeneği ,git hash-object 'in işlenecek içeriği stdin’den almasını sağlar; aksi takdirde komut, kullanılacak içeriği içeren komutun sonundaki bir dosya adı argümanı beklerdi.

Yukarıdaki komutun çıktısı 40 karakterlik bir kontrol toplamı özeti olacaktır. Bu SHA-1 özeti, birazdan öğreneceğiniz üzere, depoladığınız içeriğin bir başlık ile birlikte kontrol toplamıdır. Şimdi Git’in verilerinizi nasıl sakladığını görebilirsiniz:

$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

Yeniden objects dizinini inceleyerseniz, artık bu yeni içerik için bir dosya içerdiğini görebilirsiniz. Git, içeriği başlangıçta bu şekilde depolar; içeriğin SHA-1 sağlama toplamı ve başlığıyla adlandırılan, içerik parçası başına tek bir dosya olarak. Alt dizin, SHA-1’in ilk 2 karakteriyle adlandırılır ve dosya adı kalan 38 karakterdir.

Nesne veritabanınızda içerik olduğunda, bu içeriği git cat-file komutu ile inceleyebilirsiniz. Bu komut, Git nesnelerini incelemek için bir tür İsviçre çakısı gibidir. cat-file komutuna -p geçirmek, komutun önce içeriğin türünü belirlemesini ve ardından uygun şekilde görüntülemesini sağlar:

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

Şimdi, içerik ekleyebilir ve tekrar çıkarabilirsiniz. Bu işlemi dosyalardaki içerikle de yapabilirsiniz. Örneğin, bir dosyada basit bir sürüm kontrolü yapabilirsiniz. İlk olarak, yeni bir dosya oluşturun ve içeriğini veritabanınıza kaydedin:

$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30

Sonra, dosyaya yeni bir içerik yazın ve yeniden kaydedin:

$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a

Artık nesne veritabanınız bu yeni dosyanın her iki sürümünü de içeriyor (oraya kaydettiğiniz ilk içeriği de):

$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

Şimde, test.txt dosyasının yerel kopyasını silebilir ve ardından Git’i kullanarak, nesne veritabanından oraya kaydettiğiniz ilk sürümü geri alabilirsiniz:

$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1

ya da ikinci sürümü:

$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2

Ancak dosyanın her sürümü için SHA-1 anahtarını hatırlamak pratik değildir; ek olarak, sisteminizde dosya adını değil, yalnızca içeriği saklıyorsunuz. Bu nesne türüne blob denir. Herhangi bir nesnenin Git’teki SHA-1 anahtarını verildiğinde, git cat-file -t komutuyla Git’ten size bu nesnenin türünü söylemesini isteyebilirsiniz:

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

Ağaç (tree) Nesneleri

İnceleyeceğimiz sıradaki Git nesne türü sadece dosya adını saklama sorununu çözmekle kalmayıp, aynı zamanda bir grup dosyayı bir arada saklamanıza izin veren tree'dir (ağaç). Git içeriği UNIX dosya sistemi gibi bir şekilde saklar, ancak biraz daha basitleştirilmiştir. Tüm içerikler ağaç ve blob (damla) nesneleri olarak saklanır, ağaçlar UNIX dizin girişlerine, bloblar ise aşağı yukarı inode’lara veya dosya içeriklerine karşılık gelir. Tek bir ağaç nesnesi, her biri bir blob veya alt ağacın SHA-1 özeti olan ve ilişkili mod, tür ve dosya adı olan bir veya daha fazla girişi içerir. Örneğin, bir projedeki en son ağaç şöyle görünebilir:

$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README
100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib

master^{tree} sözdizimi, master dalındaki son katkının işaret ettiği ağaç nesnesini belirtir. lib alt dizininin bir blob olmadığını, ancak başka bir ağaca işaret ettiğini fark edin:

$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb
Not

Hangi shell kullandığınıza bağlı olarak, master^{tree} sözdizimini kullanırken hatalarla karşılaşabilirsiniz.

Windows’taki CMD’de, ^ kaçınma karakteri olarak kullanıldığından, bundan sakınmak için iki kez kullanmalısınız: git cat-file -p master^^{tree}. PowerShell kullanırken, {} karakterlerini içeren parametrelerin, parametrenin yanlış ayrıştırılmasını önlemek için alıntılanması gerekir: git cat-file -p 'master^{tree}'.

Eğer ZSH kullanıyorsanız, ^ karakteri globbing için kullanıldığı için tüm ifadeyi tırnak içine almanız gerekir: git cat-file -p "master^{tree}".

Kavramsal olarak Git’in depoladığı veriler şuna benzer:

Git veri modelinin basit versiyonu.
Görsel 148. Git veri modelinin basit versiyonu.

Kendi ağacınızı oldukça kolay bir şekilde oluşturabilirsiniz. Git normalde izlem alanınızın veya indeksinizin durumunu alarak ve bir dizi ağaç nesnesi yazarak bir ağaç oluşturur. Dolayısıyla, bir ağaç nesnesi oluşturmak için önce bazı dosyaları izlem alanına almanız gerekir. Tek girişli bir dizin (test.txt dosyanızın ilk sürümü) oluşturmak için tesisat komutu git update-index kullanabilirsiniz. Bu komutu daha önceki test.txt dosyasının önceki sürümünü, yapay olarak yeni bir izlem alanına eklemek için kullanırsınız. Dosya henüz izlem alanınızda bulunmadığından (test.txt dosyanızın henüz bir izlem alanı dahi yoktur) --add seçeneğini ve eklediğiniz dosya dizininizde değil de veritabanınızda olduğundan --cacheinfo seçeneğini geçirmeniz gerekir. Ardından da modu, SHA-1’i ve dosya adını belirtirsiniz:

$ git update-index --add --cacheinfo 100644 \
  83baae61804e65cc73a7201a7252750c76066a30 test.txt

Bu durumda, 100644 modunu belirtiyorsunuz, bu da normal bir dosya olduğunu gösterir. Diğer seçenekler arasında, yürütülebilir bir dosya olduğunu gösteren 100755; ve sembolik bir bağlantıyı belirten 120000 bulunmaktadır. Modlar normal UNIX modlarından alınmıştır ancak çok daha esnektir: bu üç mod, Git’teki dosyalar (bloblar) için geçerli olan tek modlardır (ancak dizinler ve alt modüller için başka modlar kullanılır).

Şimdi, izlem alanını bir ağaç nesnesine yazmak için git write-tree kullanabilirsiniz. Eğer bu ağaç henüz mevcut değilse, bu komutu çağırmak için -w seçeneğine gerek yoktur. Bu komut indeksin durumundan otomatik olarak bir ağaç nesnesi oluşturur:

$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30      test.txt

Yukarıda gördüğünüz aynı git cat-file komutunu kullanarak bunun bir ağaç nesnesi olduğunu da doğrulayabilirsiniz:

$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

Şimdi, test.txt'nin ikinci sürümü ve yeni bir dosya ile yeni bir ağaç oluşturacaksınız:

$ echo 'new file' > new.txt
$ git update-index --add --cacheinfo 100644 \
  1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt

İzlem alanınız artık test.txt 'nin yeni sürümüne ve new.txt adlı yeni bir dosyaya sahip. Bu ağacı yazın (izlem alanının durumunu veya dizinini bir ağaç nesnesine kaydederek) ve nasıl göründüğüne bakın:

$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

Bu ağaçta hem dosya girişleri bulunduğunu hem de test.txt SHA-1’inin daha önceki "versiyon 2" SHA-1 olduğuna dikkat edin (1f7a7a). Sırf eğlencesine, ilk ağacı bu ağacın bir alt dizini olarak ekleyeceksiniz. git read-tree çağırarak ağaçları izlem alanınıza okutabilirsiniz. Bu durumda, bu komutla --prefix seçeneğini kullanarak mevcut bir ağacı izlem alanınıza bir alt ağaç olarak okutabilirsiniz:

$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579      bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

Eğer yazdığınız yeni ağaçtan bir çalışma dizini oluşturursanız, üst düzeydeki iki dosyayı ve bak adında bir alt dizini alırsınız ve bu alt dizin, test.txt dosyasının ilk sürümünü içerir. Git’in bu yapılar için içerdiği veriyi şöyle düşünebilirsiniz:

Mevcut Git verilerinizin içerik yapısı.
Görsel 149. Mevcut Git verilerinizin içerik yapısı.

katkı Nesnesi

Yukarıdakilerin hepsini yaptıysanız, şimdi takip etmek istediğiniz proje pozlarını temsil eden üç ağacınız var, ancak daha önceki sorun hala mevcut: pozları hatırlamak için üç SHA-1 değerini de hatırlamanız gerekir. Ayrıca, pozları kimin, ne zaman ve neden kaydettiği hakkında hiçbir bilginiz yok. Bu, katkı nesnesinin sizin için sakladığı temel bilgidir.

Bir katkı nesnesi oluşturmak için commit-tree çağırırsınız, ardından tek bir ağaç SHA-1’i ve varsa hangi katkı nesnelerinin doğrudan ondan önce geldiğini belirtirsiniz. İlk yazdığınız ağaçla başlayın:

$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d

Farklı oluşturma zamanı ve yazar verisi bulunduğundan farklı bir karmaya sahip olacaksınız. Bu bölümdeki diğer yerlerde, kendi kontrol toplamalarınızla katkı ve etiket karma değerlerini değiştirin. Şimdi yeni katkı nesnenizi git cat-file ile inceleyebilirsiniz:

$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700

first commit

Bir katkı nesnesinin formatı basittir: o anki projenin pozu için en üst düzey ağacı belirtir; varsa üst katkılar (yukarıdaki katkı nesnesinin herhangi bir üstü yoktur); yazar/katkılayan bilgisi (ki bu, user.name ve user.email yapılandırma ayarlarınızı ve bir zaman damgasını kullanır); bir boş satır ve ardından katkı mesajı.

Sonraki adımda, her biri doğrudan önündeki katkıyı referans alan iki katkı nesnesini yazacaksınız:

$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'third commit'  | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9

Her üç katkı nesnesi de oluşturduğunuz üç poz ağacından birine işaret eder. İlginç bir şekilde, şimdi gerçek bir Git geçmişiniz var ve bunu son katkı SHA-1’i üzerinde çalıştırarak git log komutu ile görebilirsiniz:

$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700

	third commit

 bak/test.txt | 1 +
 1 file changed, 1 insertion(+)

commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:14:29 2009 -0700

	second commit

 new.txt  | 1 +
 test.txt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:09:34 2009 -0700

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)

Harika. Sadece arayüz komutlarını kullanmadan bir Git geçmişini oluşturmak için düşük seviyeli işlemleri gerçekleştirdiniz. Bu, aslında git add ve git commit komutlarını çalıştırdığınızda Git’in yaptığı şeydir (değişen dosyalar için bloblar saklar, indeksi günceller, ağaçları yazarak ve önceki katkıları referans alan katkı nesneleri yazarak üst düzey ağaçları ve onlardan hemen önce gelen katkıları referanslar). Bu üç ana Git nesnesi - blob, ağaç ve katkı - başlangıçta .git/objects dizininizde ayrı dosyalar olarak saklanır. İşte örnek dizindeki tüm nesneler, sakladıkları verilere açıklama eklenmiş şekilde:

$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

Tüm içsel işaretçileri takip ederseniz, yaklaşık şunun gibi bir nesne grafiği elde edersiniz:

Git dizininizdeki ulaşılabilir tüm nesneler.
Görsel 150. Git dizininizdeki ulaşılabilir tüm nesneler.

Nesne Depolama

Önceki bölümde, Git nesne veritabanınıza her katkıladığınız nesneyle bir başlık depolandığını belirttik. Git’in nesnelerini nasıl sakladığına bir göz atalım. Bir blob nesnesini (bu durumda, "what is up, doc?" dizesi) etkileşimli olarak Ruby betik dili içinde nasıl saklayacağınızı göreceksiniz.

Etkileşimli Ruby modunu irb komutu ile başlatabilirsiniz:

$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"

Git öncelikle bir başlık oluşturur, bu başlık nesnenin türünü belirleyen kısmı içerir (burada bir blob). Bu başlık kısmına, Git içeriğin bayt cinsinden boyutunu belirten bir boşluk ekler ve son olarak bir null (geçersiz) baytı ekler:

>> header = "blob #{content.length}\0"
=> "blob 16\u0000"

Git başlık ve orijinal içeriği birleştirir ve ardından bu yeni içeriğin SHA-1 karmasını hesaplar. Ruby’de bir dizenin SHA-1 değerini hesaplayabilirsiniz, bunun için SHA1 digest kütüphanesini require komutu ile içe aktarmanız ve ardından Digest::SHA1.hexdigest() ile dizeyi kullanmanız gerekir:

>> store = header + content
=> "blob 16\u0000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"

Hadi bunu git hash-object çıktısı ile karşılaştıralım. Burada echo -n kullanarak girişe yeni bir satır eklenmesini önleyeceğiz.

$ echo -n "what is up, doc?" | git hash-object --stdin
bd9dbf5aae1a3862dd1526723246b20206e5fc37

Git yeni içeriği zlib ile sıkıştırır, bu işlemi Ruby’de zlib kütüphanesi ile yapabilirsiniz. İlk olarak, kütüphaneyi içe aktarmanız ve ardından içeriği Zlib::Deflate.deflate() üzerinde çalıştırmanız gerekir:

>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"

Son olarak, zlib ile sıkıştırılmış içeriğinizi diske bir nesne olarak yazacaksınız. Yazmak istediğiniz nesnenin yolunu belirleyeceksiniz (SHA-1 değerinin ilk iki karakteri alt dizin adı olacak ve son 38 karakter ise o dizin içindeki dosya adı olacak). Eğer mevcut değilse, FileUtils.mkdir_p() işlevini kullanarak Ruby’de alt dizini oluşturabilirsiniz. Ardından, File.open() ile dosyayı açın ve elde edilen dosya tanesi üzerinde write() çağrısı ile önceden zlib ile sıkıştırılmış içeriği dosyaya yazın:

>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32

Gelin, git cat-file kullanarak nesnenin içeriğini kontrol edelim:

---
$ git cat-file -p bd9dbf5aae1a3862dd1526723246b20206e5fc37
what is up, doc?
---

İşte bu kadar! Geçerli bir Git blob nesnesi oluşturdunuz.

Tüm Git nesneleri farklı türlerle olsa dahi (başlık blob dizesi yerine, katkı veya ağaç ile başlayacaktır) aynı şekilde depolanır. Ayrıca, blob içeriği neredeyse her şey olabilirken, katkı ve ağaç içeriği çok belirli bir formata sahiptir.

scroll-to-top