Chapters ā–¾ 2nd Edition

10.2 Git Internals - Git Objects

Git Objects

Git je obsahově adresovatelný systĆ©m souborÅÆ. Výborně. A co to znamenĆ”? ZnamenĆ” to, že v jĆ”dru systĆ©mu Git se nachĆ”zĆ­ jednoduchĆ© ĆŗložiÅ”tě dat, ke kterĆ©mu lze přistupovat pomocĆ­ klĆ­ÄÅÆ. Můžete do něj vložit jakýkoli obsah a na oplĆ”tku dostanete klƭč, který můžete kdykoli v budoucnu použít k vyzvednutĆ­ obsahu. Abychom to předvedli, můžete použít nĆ­zkoĆŗrovňový příkaz hash-object, který vezme určitĆ” data, uloží je v adresÔři .git a vrĆ”tĆ­ vĆ”m klƭč, pod nĆ­mž jsou tato data uložena. Vytvořme nejprve nový repozitÔř Git. Můžeme se přesvědčit, že je adresÔř objects prĆ”zdný:

$ 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 inicializoval adresÔř objects a vytvořil v něm podadresÔře pack a info, nenajdeme tu vÅ”ak žÔdnĆ© skutečnĆ© soubory. NynĆ­ můžete uložit kousek textu do databĆ”ze Git:

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

Parametr -w sděluje příkazu hash-object, aby objekt uložil. Bez parametru by vĆ”m příkaz jen sdělil, jaký klƭč by byl přidělen. --stdin tells the command to read the content from stdin; if you don’t specify this, hash-object expects a file path at the end. Výstupem příkazu je 40znakový otisk kontrolnĆ­ho součtu (checksum hash). This is the SHA-1 hash – a checksum of the content you’re storing plus a header, which you’ll learn about in a bit. NynĆ­ se můžete podĆ­vat, jak Git vaÅ”e data uložil:

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

VidĆ­te, že v adresÔři objects přibyl nový soubor. This is how Git stores the content initially – as a single file per piece of content, named with the SHA-1 checksum of the content and its header. The subdirectory is named with the first 2 characters of the SHA-1, and the filename is the remaining 38 characters.

Obsah můžete ze systĆ©mu Git zase vytĆ”hnout, k tomu slouží příkaz cat-file. Tento příkaz je něco jako Å”výcarský nůž k prohlíženĆ­ objektÅÆ Git. PřidĆ”te-li k příkazu cat-file parametr -p, říkĆ”te mu, aby zjistil typ obsahu a přehledně vĆ”m ho zobrazil:

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

NynĆ­ tedy umĆ­te vložit do systĆ©mu Git určitý obsah a ten potĆ© zase vytĆ”hnout. Totéž můžete udělat takĆ© s obsahem v souborech. Na souboru můžete například provĆ”dět jednoduchĆ© verzovĆ”nĆ­. Vytvořte nový soubor a uložte jeho obsah do svĆ© databĆ”ze:

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

Poté do souboru zapiŔte nový obsah a znovu ho uložte:

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

VaÅ”e databĆ”ze obsahuje dvě novĆ© verze souboru a poÄĆ”tečnĆ­ obsah, který jste do nĆ­ vložili:

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

Soubor nyní můžete vrÔtit do první verze:

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

Nebo do druhƩ verze:

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

But remembering the SHA-1 key for each version of your file isn’t practical; plus, you aren’t storing the filename in your system – just the content. Tento typ objektu se nazývĆ” blob. ZadĆ”te-li příkaz cat-file -t v kombinaci s klƭčem SHA-1 objektu, Git vĆ”m sdělĆ­ jeho typ, aÅ„ se jednĆ” o jakýkoli objekt Git.

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

Tree Objects

The next type we’ll look at is the tree, which solves the problem of storing the filename and also allows you to store a group of files together. Git uklĆ”dĆ” obsah podobným zpÅÆsobem jako systĆ©m souborÅÆ UNIX, jen trochu jednoduÅ”eji. VeÅ”kerý obsah se uklĆ”dĆ” v podobě objektÅÆ typu strom a blob. Stromy odpovĆ­dajĆ­ položkĆ”m v adresÔři UNIX a bloby vĆ­cemĆ©ně odpovĆ­dajĆ­ i-uzlÅÆm nebo obsahÅÆm souborÅÆ. Jeden objekt stromu obsahuje jednu nebo vĆ­ce položek stromu, z nichž každĆ” obsahuje ukazatel SHA-1 na blob nebo podstrom s asociovaným režimem, typem a nĆ”zvem souboru. For example, the most recent tree in a project may look something like this:

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

Syntaxe master^{tree} určuje objekt stromu, na nějž ukazuje poslednĆ­ revize větve master. Notice that the lib subdirectory isn’t a blob but a pointer to another tree:

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

Conceptually, the data that Git is storing is something like this:

Simple version of the Git data model.
Figure 149. Simple version of the Git data model.

You can fairly easily create your own tree. Git normally creates a tree by taking the state of your staging area or index and writing a series of tree objects from it. Proto chcete-li vytvořit objekt stromu, musĆ­te ze vÅ”eho nejdříve připravit soubory k zapsĆ”nĆ­, a vytvořit tak index. To create an index with a single entry – the first version of your test.txt file – you can use the plumbing command update-index. You use this command to artificially add the earlier version of the test.txt file to a new staging area. You must pass it the --add option because the file doesn’t yet exist in your staging area (you don’t even have a staging area set up yet) and --cacheinfo because the file you’re adding isn’t in your directory but is in your database. K tomu vÅ”emu přidĆ”te režim, SHA-1 a nĆ”zev souboru:

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

In this case, you’re specifying a mode of 100644, which means it’s a normal file. Other options are 100755, which means it’s an executable file; and 120000, which specifies a symbolic link. The mode is taken from normal UNIX modes but is much less flexible – these three modes are the only ones that are valid for files (blobs) in Git (although other modes are used for directories and submodules).

NynĆ­ můžete použít příkaz write-tree, jĆ­mž zapĆ­Å”ete stav oblasti připravovaných změn neboli indexu do objektu stromu. No -w option is needed – calling write-tree automatically creates a tree object from the state of the index if that tree doesn’t yet exist:

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

Můžete si takĆ© ověřit, že jde skutečně o objekt stromu:

$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

You’ll now create a new tree with the second version of test.txt and a new file as well:

$ echo 'new file' > new.txt
$ git update-index test.txt
$ git update-index --add new.txt

Your staging area now has the new version of test.txt as well as the new file new.txt. Uložte tento strom (zaznamenĆ”nĆ­m stavu oblasti připravených změn neboli indexu do objektu stromu) a prohlĆ©dněte si výsledek:

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

Notice that this tree has both file entries and also that the test.txt SHA-1 is the ā€œversion 2ā€ SHA-1 from earlier (1f7a7a). Just for fun, you’ll add the first tree as a subdirectory into this one. Stromy můžete do oblasti připravených změn načƭst příkazem read-tree. V tomto případě můžete načƭst existujĆ­cĆ­ strom jako podstrom do oblasti připravených změn pomocĆ­ parametru --prefix, který zadĆ”te k příkazu read-tree:

$ 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

If you created a working directory from the new tree you just wrote, you would get the two files in the top level of the working directory and a subdirectory named bak that contained the first version of the test.txt file. You can think of the data that Git contains for these structures as being like this:

The content structure of your current Git data.
Figure 150. The content structure of your current Git data.

Commit Objects

MĆ”te vytvořeny tři stromy označujĆ­cĆ­ rÅÆznĆ© snĆ­mky vaÅ”eho projektu, jež chcete sledovat. PÅÆvodnĆ­ho problĆ©mu jsme se vÅ”ak stĆ”le nezbavili: musĆ­te si pamatovat vÅ”echny tři hodnoty SHA-1, abyste mohli snĆ­mky znovu vyvolat. You also don’t have any information about who saved the snapshots, when they were saved, or why they were saved. Toto jsou zĆ”kladnĆ­ informace, kterĆ© obsahuje objekt revize.

Pro vytvořenĆ­ objektu revize zavolejte příkaz commit-tree a zadejte jeden SHA-1 stromu a eventuĆ”lnĆ­ objekty revize, kterĆ© mu bezprostředně předchĆ”zely. Začněte prvnĆ­m stromem, který jste zapsali:

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

You will get a different hash value because of different creation time and author data. Replace commit and tag hashes with your own checksums further in this chapter. NynĆ­ se můžete podĆ­vat na nově vytvořený objekt revize. Použijte příkaz cat-file:

$ 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

The format for a commit object is simple: it specifies the top-level tree for the snapshot of the project at that point; the author/committer information (which uses your user.name and user.email configuration settings and a timestamp); a blank line, and then the commit message.

Next, you’ll write the other two commit objects, each referencing the commit that came directly before it:

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

VÅ”echny tři tyto objekty revizĆ­ ukazujĆ­ na jeden ze tří stromÅÆ snĆ­mku, který jste vytvořili. Může se to zdĆ”t zvlÔŔtnĆ­, ale nynĆ­ mĆ”te vytvořenu skutečnou historii revizĆ­ Git, kterou lze zobrazit příkazem git log spuÅ”těným pro hodnotu SHA-1 poslednĆ­ revize:

$ 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(+)

ÚžasnĆ©! You’ve just done the low-level operations to build up a Git history without using any of the front end commands. This is essentially what Git does when you run the git add and git commit commands – it stores blobs for the files that have changed, updates the index, writes out trees, and writes commit objects that reference the top-level trees and the commits that came immediately before them. These three main Git objects – the blob, the tree, and the commit – are initially stored as separate files in your .git/objects directory. Toto jsou vÅ”echny objekty v ukĆ”zkovĆ©m adresÔři spolu s komentÔřem k tomu co obsahujĆ­:

$ 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

If you follow all the internal pointers, you get an object graph something like this:

All the objects in your Git directory.
Figure 151. All the objects in your Git directory.

UklÔdÔní objektů

We mentioned earlier that a header is stored with the content. Let’s take a minute to look at how Git stores its objects. You’ll see how to store a blob object – in this case, the string ā€œwhat is up, doc?ā€ – interactively in the Ruby scripting language.

InteraktivnĆ­ režim Ruby spustĆ­te příkazem irb:

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

Git vytvoří zĆ”hlavĆ­, kterĆ© bude začƭnat typem objektu, jĆ­mž je v naÅ”em případě blob. PotĆ© vloží mezeru, za níž bude nĆ”sledovat velikost obsahu a na konec nulový byte:

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

Git vytvoří řetězec ze zĆ”hlavĆ­ a pÅÆvodnĆ­ho obsahu a vypočƭtĆ” kontrolnĆ­ součet SHA-1 tohoto novĆ©ho obsahu. V Ruby můžete hodnotu SHA-1 danĆ©ho řetězce spočƭtat tak, že příkazem require připojĆ­te knihovnu pro počƭtĆ”nĆ­ SHA1 a zavolĆ”te Digest::SHA1.hexdigest() s daným řetězcem:

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

Git zkomprimuje nový obsah metodou zlib, kterĆ” je obsažena v knihovně zlib. Nejprve je třeba vyžÔdat si knihovnu a potĆ© na obsah spustit příkaz Zlib::Deflate.deflate():

>> 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"

Finally, you’ll write your zlib-deflated content to an object on disk. You’ll determine the path of the object you want to write out (the first two characters of the SHA-1 value being the subdirectory name, and the last 38 characters being the filename within that directory). In Ruby, you can use the FileUtils.mkdir_p() function to create the subdirectory if it doesn’t exist. PotĆ© zadejte File.open() pro otevřenĆ­ souboru a volĆ”nĆ­m write() na vzniklý identifikĆ”tor souboru zapiÅ”te do souboru prĆ”vě zkomprimovaný (zlib) obsah:

>> 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

That’s it – you’ve created a valid Git blob object. All Git objects are stored the same way, just with different types – instead of the string blob, the header will begin with commit or tree. A navĆ­c, zatĆ­mco obsahem blobu může být tĆ©měř cokoliv, obsah revize nebo stromu mĆ” velmi specifický formĆ”t.

scroll-to-top