los_t ([info]los_t) wrote,
@ 2007-08-14 15:19:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:git guts

Git Guts (Part 3)
В первой части я уже упоминал, что репозиторий git представляет собой картотеку объектов, объединенных ссылками друг на друга.


Из четырех типов объектов в git (blob, tree, commit, tag) только blob-ы не могут содержать ссылки. Все остальные объекты, по сути, являются просто ссылками либо на blob-ы, либо на другие ссылки.

Мы уже знаем, что blob-ы включают в себя только содержание файла, но не его имя, или режимы доступа. Вся информация об именах содержится в объектах-деревьях (tree). Фактически, деревья аналогичны понятию "каталог" в файловой системе, так же как blob-ы аналогичны понятию inode.

Объекты-деревья могут хранить внутри себя как ссылки на blob-ы, так и ссылки на другие объекты-деревья. В результате можно построить иерархию деревьев, аналогичную иерархии каталогов и файлов.

Объект-дерево представляет собой список элементов, состоящих из четырех полей:


  1. mode (режим доступа) - представляет собой права UNIX на объект-ссылку, плюс несколько дополнительных битов, позволяющих хранить в гите символические ссылки. Записывается в виде шести цифр, из которых первые три описывают тип объекта, а оставшиеся - права UNIX. Правда мне ни разу не удалось увидеть, чтобы значение третьей цифры было отлично от нуля, так что я не знаю что она означает.
    Первая цифра - 1 для файлов и символических, 0 для директорий.
    Вторая цифра - 0 для файлов, 2 для символических ссылок, 4 - для директорий

  2. Тип объекта, на который ссылается элемент списка. Может быть blob или tree.

  3. SHA1 хеш объекта. Собственно, это и является ссылкой, так как однозначно определяет объект в репозитории git.

  4. имя объекта. Имя файла для blob-ов, имя директории для tree.


Объект-дерево после создания получает свое имя-хеш, и может быть после этого включен в другие деревья.

Создать новый объект-дерево можно с нуля, используя команду git-mktree. Ей на вход (stdin) надо передать текстовый список, в котором каждая строчка описывает один элемент. Первые три поля должны быть разделены пробелами, а последнее - имя объекта - должно быть отделено табом.

Вот пример:
$ mkdir ~/tmp/gitgut3
$ cd ~/tmp/gitgut3
$ git-init
Initialized empty Git repository in .git/

$ echo "File1" > file1
$ echo "File2" > file2

$ git-hash-object -w file1
03f128cf48cb203d938805e9f3e13b808d1773e9
$ git-hash-object -w file2
b973e639605e63466ea5ba09b04a545f16946ca8

$ echo -e "100640 blob 03f128cf48cb203d938805e9f3e13b808d1773e9\tfile1
100640 blob b973e639605e63466ea5ba09b04a545f16946ca8\tfile2" | git-mktree

b2efb2a7e48025c4d185080412a6ba1121ee6c59


Как видно из примера, команде git-mktree нужно подать на стандартный вход содержимое создавамого объекта-дерева, что я и сделал командой echo.
Полученный объект-дерево теперь присутствует в базе:

$ ls .git/objects/b2/efb2a7e48025c4d185080412a6ba1121ee6c59 
.git/objects/b2/efb2a7e48025c4d185080412a6ba1121ee6c59


Его содержимое можно посмотреть, используя команду git-ls-tree

$ git-ls-tree b2efb2a7e48025c4d185080412a6ba1121ee6c59     
100640 blob 03f128cf48cb203d938805e9f3e13b808d1773e9    file1
100640 blob b973e639605e63466ea5ba09b04a545f16946ca8    file2


Чтобы далеко не уходить, покажу, чем же полезно, что деревья являются именно объектами, с именами-хешами.

Например, если у двух объектов-деревьев одинаковое имя-хеш, что это означает? Что внутренности этих деревьев совпадают! А так как внутренности деревьев - это ссылки на объекты, то это означает что два дерева ссылаются на одни и те же объекты. Которые в свою очередь тоже могут быть деревьями или блобами. Таким образом имя-хеш дерева на самом деле идентифицирует не только "файлы в директории", но и все файлы во всех поддиректориях этой директории - одно имя для всех иерархии!

Это свойство позволяет git-у очень быстро производить сравнение деревьев со сколь угодно сложной иерархией, уровнями вложенности и т.д. без чтения собственно содержимого - blob-ов или tree.

Например, я создаю новое дерево, которое отличается от старого дерева b2efb2a7e4... тем, что в содержимое file2 была добавлена дополнительная строчка, а файл file1 переименован в file3.

$ echo Secondline >> file2
$ git-hash-object -w file2
4dd2746869211aedfec0f07afb12a879c09569e7

$ echo -e "100640 blob 03f128cf48cb203d938805e9f3e13b808d1773e9\tfile3
100640 blob 4dd2746869211aedfec0f07afb12a879c09569e7\tfile2" | git-mktree

493a5292de0b743e77aa190921da56d33599b59e

$ git-ls-tree 493a5292de0b743e77aa190921da56d33599b59e

100640 blob 4dd2746869211aedfec0f07afb12a879c09569e7    file2
100640 blob 03f128cf48cb203d938805e9f3e13b808d1773e9    file3


Давайте посмотрим, как git может легко вычислить разницу между этими деревьями. используя только объекты-деревья.
Для этого сохраним выводы git-ls-tree для каждого дерева в отдельный файл и натравим на них команду diff -u.

$ git-ls-tree b2efb2a7e48025c4d185080412a6ba1121ee6c59 > tree1
$ git-ls-tree 493a5292de0b743e77aa190921da56d33599b59e > tree2
$ diff -u tree1 tree2
--- tree1       2007-08-14 14:55:06 +0400
+++ tree2       2007-08-14 14:55:30 +0400
@@ -1,2 +1,2 @@
-100640 blob 03f128cf48cb203d938805e9f3e13b808d1773e9   file1
-100640 blob b973e639605e63466ea5ba09b04a545f16946ca8   file2
+100640 blob 4dd2746869211aedfec0f07afb12a879c09569e7   file2
+100640 blob 03f128cf48cb203d938805e9f3e13b808d1773e9   file3

Итак, видно, что по сравнению с деревом 1 в дереве два исчез file1, у file2 изменился SHA1 хеш, и добавился новый file3.
Также можно заметить, что у удаленного файла file1 и добавленного файла file3 одинаковый SHA1 хеш - отсюда можно сделать вывод, что было произведено переименование из file1 в file3 без изменения содержимого.

Точно такую же работу производит и git, точнее его команда git-diff-tree. Она выводит разницу между двумя деревьями в читабельном для человека виде.

$ git-diff-tree b2efb2a7e48025c4d185080412a6ba1121ee6c59 493a5292de0b743e77aa190921da56d33599b59e
:100644 000000 03f128cf48cb203d938805e9f3e13b808d1773e9 0000000000000000000000000000000000000000 D      file1
:100644 100644 b973e639605e63466ea5ba09b04a545f16946ca8 4dd2746869211aedfec0f07afb12a879c09569e7 M      file2
:000000 100644 0000000000000000000000000000000000000000 03f128cf48cb203d938805e9f3e13b808d1773e9 A      file3


Если git-diff-tree вызывать с ключом -p, то она сгенерирует патч, который будучи применен к tree1, приведет его к tree2.
git-diff-tree -p b2efb2a7e48025c4d185080412a6ba1121ee6c59 493a5292de0b743e77aa190921da56d33599b59e
diff --git a/file1 b/file1
deleted file mode 100644
index 03f128c..0000000
--- a/file1
+++ /dev/null
@@ -1 +0,0 @@
-File1
diff --git a/file2 b/file2
index b973e63..4dd2746 100644
--- a/file2
+++ b/file2
@@ -1 +1,2 @@
 File2
+Secondline
diff --git a/file3 b/file3
new file mode 100644
index 0000000..03f128c
--- /dev/null
+++ b/file3
@@ -0,0 +1 @@
+File1


Как видно по патчу, git-diff-tree не учел, что файл file1 был переименован в file3, и сгенерировал патч так, как будто file1 удалили, и file3 добавили заново.

Но как мы знаем, blob-ы у file1 и file3 совпадают - поэтому можно точно сказать что было переименование. Для того, чтобы git-diff-tree стал обращать на это внимание, ему надо передать ключик -M (detect renames).

Тогда он сгенерирует особый патч-переименование. К сожалению, стандартная команда patch не может прикладывать такие патчи-переименования, так что потребуется прикладывать этот патч к дереву с помощью команды git-apply.

$ git-diff-tree -M -p b2efb2a7e48025c4d185080412a6ba1121ee6c59 493a5292de0b743e77aa190921da56d33599b59e
diff --git a/file2 b/file2
index b973e63..4dd2746 100644
--- a/file2
+++ b/file2
@@ -1 +1,2 @@
 File2
+Secondline
diff --git a/file1 b/file3
similarity index 100%
rename from file1
rename to file3


Итак, объекты-деревья служат для объединения blob-ов и других деревьев в иерархию, аналогичную файловой системе. Деревья хранят биты доступа, хеши содержимого и имена объектов, поэтому между двумя деревьями может быть разница только по этим параметрам.

Такие параметры как времена создания, изменения и доступа файла, а также создатель или владелец файла, в деревьях не записываются. Некоторое подобие такой информации хранят объекты-commit'ы, о которых я расскажу в следующий раз.

А пока вам домашнее задание: создайте пустое объект-дерево (вообще без файлов) и запостите сюда его SHA1


Продолжение следует.



(Post a new comment)


[info]svpv
2007-08-14 12:02 pm UTC (link)
Похожо ты не знаеш што такое inode. Inode это такая структура которая состоит из struct stat + адресации блоков. То есть это struct stat, у которой в конце ещё дописан массив адресации дисковых блоков. Аналогия с blob очень шаткая. Кстати каталог это тоже inode, попробуй ls -di /.

(Reply to this) (Thread)


[info]los_t
2007-08-14 12:23 pm UTC (link)
Аналогия inode с blob в том, что они показывают только содержимое файла, не неся никакой информации об имени файла. Имена файлов содержатся только в каталоге. Поэтому на одной и той же ФС возможны жесткие ссылке. В git-е можно считать, что все одинаковые файлы или деревья hardlink-нуты между собой.

(Reply to this) (Parent)(Thread)

(Reply from suspended user)

[info]evg_krsk
2007-08-25 10:08 am UTC (link)
Ждём продолжения...

(Reply to this)

пустое объект-дерево (вообще без файлов) и запостите сю
(Anonymous)
2007-12-19 02:10 pm UTC (link)
echo -n | git-mktree
4b825dc642cb6eb9a060e54bf8d69288fbee4904

угадал? :]

(Reply to this) (Thread)

Re: пустое объект-дерево (вообще без файлов) и запостите
[info]los_t
2007-12-19 03:17 pm UTC (link)
Угу, только можно было проще - git-mktree </dev/null

(Reply to this) (Parent)(Thread)

Re: пустое объект-дерево (вообще без файлов) и запостите
(Anonymous)
2008-01-20 03:08 pm UTC (link)
Ве открыли мне глаза на /dev/null, как источник !
Всю жизнь его использовал только как назначение,
иногда встречал как источник (тот же git-diff), но
до сегодняшнего дня не придавал этому особого
значения. Спасибо огромное за статьи о git!
Жду еще :]

(Reply to this) (Parent)(Thread)

/dev/null как источник
[info]shep256
2008-06-11 08:44 am UTC (link)
Пустые файлики создают, как
cat /dev/null > file
или
touch file
или
недавно увидел у кого-то самый короткий способ:
>file


Просто навеяло /dev/null-ом.

(Reply to this) (Parent)(Thread)

Re: /dev/null как источник
[info]abraham1901
2009-05-08 03:33 pm UTC (link)
"> newfile"
Это не безопасный способ создания файла.
Лучше экранировать:
":> newfile"

(Reply to this) (Parent)


Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…