Архитектура git-репозитория
Git-репозиторий представляет собой (грубо говоря) хранилище объектов и ссылок (представленное в виде каталога в файловой системе). Объекты бывают следующих видов:
- blob
- tree
- commit
Из ref-ов интересны следующие:
- branch
- tag
Продемонстрируем внутреннее устройство git-репозитория на примере.
Сначала создадим новый репозиторий:
% git init Initialized empty Git repository in .git/
Создадим два файла с каким-нибудь содержимым:
% echo -e "First line\nSecond line\nThird line\nFourth line\n" > file1.txt % echo -e "Second file\n-----------\n\nTest.\n" > file2.txt
Теперь добавим объекты blob в репозиторий. Объекты blob суть данные. Идентифицируются объекты SHA1-хэшем от их содержимого (первые два символа в hex-записи хэша используются как имя директории, куда кладётся объект, остальные — как имя файла объекта в этом каталоге). Таким образом, если два объекта совпадают по содержимому, то и идентификаторы у них будут одинаковы (как следствие, сохранён будет только один объект). Благодаря чудесным свойстам хэшей, обратное тоже верно с точностью до коллизий. Для создания blob и добавления его в репозиторий существует команда git hash-object.
% git hash-object -w file1.txt bdd1c7494f12d6e63756a1e7b52dc5b1cd67da82 % git hash-object -w file2.txt 3fc9237ae2f776560a581562356e6b28ba31db80
Для просмотра объектов существует команда git cat-file.
% git cat-file blob 3fc9237ae2f776560a581562356e6b28ba31db80 Second file ----------- Test.
Деревья представляют собой объекты с содержимым специального вида — набором записей вида <права> <тип> <идентификатор> <имя> (по записи на строку; в дерево могут входить blob'ы и другие деревья). Идентификатором дерева является SHA1-хэш его содержимого. Для создания дерева и добавления его в репозиторий существует команда git mktree.
% echo -e "100640 blob bdd1c7494f12d6e63756a1e7b52dc5b1cd67da82\tfile1.txt\n100640 blob 3fc9237ae2f776560a581562356e6b28ba31db80\tfile2.txt" | git mktree eee44d801c82169dd7b7709773607ea989ea4be7
Просмотр дерева осуществляется командой git ls-tree.
% git ls-tree eee44d801c82169dd7b7709773607ea989ea4be7 100640 blob bdd1c7494f12d6e63756a1e7b52dc5b1cd67da82 file1.txt 100640 blob 3fc9237ae2f776560a581562356e6b28ba31db80 file2.txt
Поменяем немного файлы и создадим дерево, их включающее.
% echo -e "Fifth line\n" >> file1.txt
Тут добавляется ещё один объект типа blob в репозиторий (при этом объект с идентификатором bdd1c7494f12d6e63756a1e7b52dc5b1cd67da82 никуда не девается):
% git hash-object -w file1.txt 7e3180bd025517cac5ef63061d39a5f2d6c4df3a
Добавляем ещё одно дерево, с новым blob и указанием другого имени для второго файла:
% echo -e "100640 blob 7e3180bd025517cac5ef63061d39a5f2d6c4df3a\tfile1.txt\n100640 blob 3fc9237ae2f776560a581562356e6b28ba31db80\tfile3.txt" | git-mktree 43e9938b3671088a7b07c6c8cd8c5e7e5c586196
Сравним деревья.
% git ls-tree eee44d801c82169dd7b7709773607ea989ea4be7 > old_tree % git ls-tree 43e9938b3671088a7b07c6c8cd8c5e7e5c586196 > new_tree % diff -u old_tree new_tree --- old_tree 2011-04-06 17:08:13.000000000 +0400 +++ new_tree 2011-04-06 17:08:21.000000000 +0400 @@ -1,2 +1,2 @@ -100640 blob bdd1c7494f12d6e63756a1e7b52dc5b1cd67da82 file1.txt -100640 blob 3fc9237ae2f776560a581562356e6b28ba31db80 file2.txt +100640 blob 7e3180bd025517cac5ef63061d39a5f2d6c4df3a file1.txt +100640 blob 3fc9237ae2f776560a581562356e6b28ba31db80 file3.txt
Для сравнения деревьев в git существует специальная команда git diff-tree.
% git diff-tree eee44d801c82169dd7b7709773607ea989ea4be7 43e9938b3671088a7b07c6c8cd8c5e7e5c586196 :100644 100644 bdd1c7494f12d6e63756a1e7b52dc5b1cd67da82 7e3180bd025517cac5ef63061d39a5f2d6c4df3a M file1.txt :100644 000000 3fc9237ae2f776560a581562356e6b28ba31db80 0000000000000000000000000000000000000000 D file2.txt :000000 100644 0000000000000000000000000000000000000000 3fc9237ae2f776560a581562356e6b28ba31db80 A file3.txt % git diff-tree -p eee44d801c82169dd7b7709773607ea989ea4be7 43e9938b3671088a7b07c6c8cd8c5e7e5c586196 diff --git a/file1.txt b/file1.txt index bdd1c74..7e3180b 100644 --- a/file1.txt +++ b/file1.txt @@ -3,3 +3,5 @@ Second line Third line Fourth line +Fifth line + diff --git a/file2.txt b/file2.txt deleted file mode 100644 index 3fc9237..0000000 --- a/file2.txt +++ /dev/null @@ -1,5 +0,0 @@ -Second file ------------ - -Test. - diff --git a/file3.txt b/file3.txt new file mode 100644 index 0000000..3fc9237 --- /dev/null +++ b/file3.txt @@ -0,0 +1,5 @@ +Second file +----------- + +Test. +
Ключ -M позволяет детектировать переименования.
% git diff-tree -p -M eee44d801c82169dd7b7709773607ea989ea4be7 43e9938b3671088a7b07c6c8cd8c5e7e5c586196 diff --git a/file1.txt b/file1.txt index bdd1c74..7e3180b 100644 --- a/file1.txt +++ b/file1.txt @@ -3,3 +3,5 @@ Second line Third line Fourth line +Fifth line + diff --git a/file2.txt b/file3.txt similarity index 100% rename from file2.txt rename to file3.txt
Перейдём к созданию коммитов. Коммит — объект с содержимым в определённом формате:
<Идентификатор объекта-дерева> <Список родительских коммитов, может быть ноль и более> <Информация об авторе> <Информация о коммитере> <Описание коммита>
Информация о авторе (человеке, явлюяющемся автором изменений) и коммитере (человеке, добавившим коммит с изменениями в репозиторий) берётся из переменных окружения или конфигурационного файла.
% export GIT_AUTHOR_NAME="Author Anonymous" % export GIT_AUTHOR_EMAIL="author@example.org" % export GIT_COMMITTER_NAME="Committer Anonymous" % export GIT_COMMITTER_EMAIL="committer@example.org" % echo "Initial commit." | git commit-tree eee44d801c82169dd7b7709773607ea989ea4be7 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 % git cat-file commit 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 tree eee44d801c82169dd7b7709773607ea989ea4be7 author Author Anonymous <author@example.org> 1302095470 +0400 committer Committer Anonymous <committer@example.org> 1302095470 +0400 Initial commit.
Идентификатором коммита всё также является хэш его содержимого.
Для просмотра истории коммитов служит команда git log.
% PAGER=cat git log 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 commit 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 Author: Author Anonymous <author@example.org> Date: Wed Apr 6 17:11:10 2011 +0400 Initial commit.
Добавим второй коммит для второго дерева. Заметим, что до этого два дерева никак связаны не были. Кроме того, информация о том, что коммиты связаны указывается исключительно в виде родителя (одного или нескольких) для данных коммитов. Таким образом, все изменения, деланные в рамках коммита, вычисляются на основании двух коммитов, а не сохраняются где-либо отдельно магическим образом.
% echo "Added fifth line, file2.txt renamed." | git commit-tree 43e9938b3671088a7b07c6c8cd8c5e7e5c586196 -p 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 2bc476f71932bf5cb83ada16cc80c704960da513 % git cat-file commit 2bc476f71932bf5cb83ada16cc80c704960da513 tree 43e9938b3671088a7b07c6c8cd8c5e7e5c586196 parent 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 author Author Anonymous <author@example.org> 1302095586 +0400 committer Committer Anonymous <committer@example.org> 1302095586 +0400 Added fifth line, file2.txt renamed. % git log 2bc476f71932bf5cb83ada16cc80c704960da513 commit 2bc476f71932bf5cb83ada16cc80c704960da513 Author: Author Anonymous <author@example.org> Date: Wed Apr 6 17:13:06 2011 +0400 Added fifth line, file2.txt renamed. commit 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 Author: Author Anonymous <author@example.org> Date: Wed Apr 6 17:11:10 2011 +0400 Initial commit.
Заметим, что указав git log второй коммит, он по идентификатору parent в коммите восстановил историю коммитов.
Создадим ссылку-ветку и указатель на текущий коммит (относительно которого развёрнута рабочая директория).
% echo 2bc476f71932bf5cb83ada16cc80c704960da513 > .git/refs/heads/master % ln -sf refs/heads/master .git/HEAD % git branch * master % git rev-parse master 2bc476f71932bf5cb83ada16cc80c704960da513 % git rev-parse HEAD 2bc476f71932bf5cb83ada16cc80c704960da513
Команда git rev-parse позволяет получить идентификатор объекта, на который ссылается ref. Как можно видеть, поскольку HEAD есть симлинк на текущий бранч, то они ссылаются на один и тот же коммит.
Создадим второй бранч, переключимся на него и добавим в него новый коммит.
% echo 2bc476f71932bf5cb83ada16cc80c704960da513 > .git/refs/heads/other % ln -sf refs/heads/other .git/HEAD % git branch master * other % echo -e "Sixth line\n" >> file1.txt % git hash-object -w file1.txt 1436c3454bd09d96e134546f8dcd3d87feb92970 % echo -e "100640 blob 1436c3454bd09d96e134546f8dcd3d87feb92970\tfile1.txt\n100640 blob 3fc9237ae2f776560a581562356e6b28ba31db80\tfile3.txt" | git mktree ba1bdfb3c0e54ae124846054b7d6195a3524e89f % echo "Sixth line added" | git commit-tree ba1bdfb3c0e54ae124846054b7d6195a3524e89f -p 2bc476f71932bf5cb83ada16cc80c704960da513 e076228e058b8edbfc120b4f4515baad783b64b5 % echo e076228e058b8edbfc120b4f4515baad783b64b5 > .git/HEAD % PAGER=cat git log --pretty=oneline e076228e058b8edbfc120b4f4515baad783b64b5 Sixth line added 2bc476f71932bf5cb83ada16cc80c704960da513 Added fifth line, file2.txt renamed. 21b04a2213a7c1381c30f5f9705a0e8d2f72b375 Initial commit.
Второй основной вид ссылок — теги. Теги создаются командой git tag.
% git tag fifth-line 2bc476f71932bf5cb83ada16cc80c704960da513
Это далеко не весь инструментарий Git, только низкоуровневая его часть. Git, как и любая другая приличная (D)VCS также включает множество инструментов для взаимодействия репозиториев, патчинга-мерджа, ретроспекции и преобразований репозитория.