понедельник, 29 марта 2010 г.

Git. part 4

Взято здесь

Git не был бы системой контроля версий, если бы не позволял хранить историю изменений деревьев.

Для хранения истории в git используются специальные объекты-commit'ы. Каждому коммиту соответствует ровно одно дерево. Коммиты также хранят информацию о "предках" этого дерева - то есть ссылки на т.н. родительские коммиты. Можно считать, что коммит указывает, из каких деревьев (их может быть несколько) произошло текущее дерево, а также кто в этом виноват (автор коммита) и по какой причине (сообщение коммита).



У самого первого коммита в репозитории не может быть предков. Он считается начальным коммитом, и считается что до него ничего не было. В репозитории git обычно бывает только один начальный коммит, а все остальные происходят из него. Можно считать начальный коммит Адамом и Евой :) У большинства коммитов предок всего один, поэтому часто история коммитов линейна. Авраам родил Исаака, Исаак родил Иакова, Иаков родил Иуду и т.д. :)

Бывает, что несколько коммитов происходят от одного предка. В этом месте в истории появляется "развилка" - история начинает делиться на ветви ("колена", если продолжать аналогию с Библией).

Но бывают еще коммиты, у которых несколько родителей. Это т.н. коммиты-слияния (merges), и в общем-то, количество родителей у коммита не ограничено. Это действие противоположно вышеописанной "развилке", и объединяет ранее разделенные ветви. Так, породнились бы Капулетти и Монтекки, если бы Вильяму Шекспиру захотелось бы устроить в "Ромео и Джульетте" хэппи-энд.

Но довольно лирики. Если говорить формально, то сам объект-коммит - это простой текст в строго определенном формате. У каждого коммита есть соответствующее дерево (первая строчка), далее перечисляются родители (каждый родитель на отдельной строчке), а дальше указываются "автор" коммита и время создания коммита.
После этого указывается т.н. "committer" - человек, который записал коммит в историю репозитория. Вместе с committer записывается и время, когда коммит был записан в историю. После чего оставшиеся строки занимает сообщение о коммите - произвольный текст, который указал автор при создании коммита.

Обычно поля committer и author совпадают, если автор сразу же после создания коммита записывает его в репозиторий. Но бывает и другая ситуация, когда один человек создает коммит, а другой применяет этот коммит к своему репозиторию. Тогда author и committer будут совершенно разными людьми. И committer и author указываются в формате Имя , который считается стандартным форматом для задания адреса электронной почты.

Создать объект-коммит можно с помощью команды git-commit-tree.

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

На вход (stdin) этой команде надо подать сообщение коммита. Остальные поля (author и commiter) команда заполняет сама. Если определенным образом не сконфигурировать git, по умолчанию в качестве имени автора будет использоваться имя текущего пользователя, а в качестве адреса электронной почты - . Также в качестве даты создания коммита и записи его в историю, будет использоваться текущая дата.

Ну что долго объяснять, вот вам пример:

$ mkdir ~/tmp/gitguts4
$ cd ~/tmp/gitguts4
$ git-init
Initialized empty Git repository in .git/

$ echo "file1" > file1
$ echo "file2" > file2

$ git-hash-object -w file1
e2129701f1a4d54dc44f03c93bca0a2aec7c5449

$ git-hash-object -w file2
6c493ff740f9380390d5c9ddef4af18697ac9375

$ echo -e "10644 blob e2129701f1a4d54dc44f03c93bca0a2aec7c5449\tfile1
10644 blob 6c493ff740f9380390d5c9ddef4af18697ac9375\tfile2" | git-mktree

eaa27839f1ccaa6e087202ec96c479ee2c93b71e

$ export GIT_AUTHOR_NAME="Git Guts"
$ export GIT_AUTHOR_EMAIL="gitguts@localhost"
$ export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
$ export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"

$ echo "Initial commit" | faketime -t 200001010000 git-commit-tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e

a215c9607c843ff00bc1490fb51271b6211070a2

Обратите внимание - я использовал задание автора и коммитера через переменные окружения, а также использовал faketime для того, чтобы задать время создания коммита и время сохранения его в репозитории. Дело в том, что если вы попытаетесь повторить мои действия, и не будете использовать переменные окружения и faketime, то в коммите будет другое время, и другие авторы/коммитеры, и вы не сможете полностью воспроизвести последующие действия, так как у коммитов будут другие имена и другое содержимое.

Посмотреть содержимое созданного объекта можно, используюя утилиту git-cat-file


$ git-cat-file commit a215c9607c843ff00bc1490fb51271b6211070a2
tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e
author Git Guts 946674000 +0300
committer Git Guts 946674000 +0300

Initial commit

Ну, думаю не стоит объяснять, где что находится в этом объекта - все и так очевидно. Созданный коммит не имеет предков - то есть является сиротой. :) Давайте создадим ему потомков, чтобы было веселее. Для того, чтобы указать родителя коммита, в параметры git-commit-tree надо добавить -p , ну например как показано в следующем примере:

$ echo "Abraham" | faketime -t 200001010100 git-commit-tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e -p a215c9607c843ff00bc1490fb51271b6211070a2

09e01781c4c8245acd0728184d7cb8d9c7579901

$ git-cat-file commit 09e01781c4c8245acd0728184d7cb8d9c7579901
tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e
parent a215c9607c843ff00bc1490fb51271b6211070a2
author Git Guts 946677600 +0300
committer Git Guts 946677600 +0300

Abraham

Итак, Авраам рожден :) Видите, в коммите добавилось поле parent, с указанием родительского коммита. Добавим же Исаака - сына его :) (для этого укажем в поле "родитель" SHA1 Авраама).

$ echo "Isaac" | faketime -t 200001010200 git-commit-tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e -p 09e01781c4c8245acd0728184d7cb8d9c7579901

420a3454070a1767c3fe7107f9dc753d8ff3722c

$ git-cat-file commit 420a3454070a1767c3fe7107f9dc753d8ff3722c
tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e
parent 09e01781c4c8245acd0728184d7cb8d9c7579901
author Git Guts 946681200 +0300
committer Git Guts 946681200 +0300

Isaac

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

Давайте теперь посмотрим на историю вновь созданного Исаака. Просмотром истории в git занимается программа-историк git-log.

$ PAGER=cat git-log 420a3454070a1767c3fe7107f9dc753d8ff3722c
commit 420a3454070a1767c3fe7107f9dc753d8ff3722c
Author: Git Guts
Date: Sat Jan 1 02:00:00 2000 +0300

Isaac

commit 09e01781c4c8245acd0728184d7cb8d9c7579901
Author: Git Guts
Date: Sat Jan 1 01:00:00 2000 +0300

Abraham

commit a215c9607c843ff00bc1490fb51271b6211070a2
Author: Git Guts
Date: Sat Jan 1 00:00:00 2000 +0300

Initial commit

Я использовал PAGER=cat, чтобы git-log не запускал для просмотра истории команду less (ну или что там у вас поставлено вместо $PAGER), а просто тупо вываливал информацию в терминал.

Итак, по выводу истории видно, что от начального коммита произошел Авраам, а от Авраама - Исаак :)

Продолжим наши уроки Ветхого завета и продемонстрируем "развилку". У Исаака, как известно, было два сына - Исав и Иаков. Исав - старший брат, Иаков - младший. Продемонстрируем это в терминах git.

$ echo "Esau" | faketime -t 200001010300 git-commit-tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e -p 420a3454070a1767c3fe7107f9dc753d8ff3722c
de10f1828d215892dcebd00c4f7738141bfd0df7

$ echo "Jakob" | faketime -t 200001010400 git-commit-tree eaa27839f1ccaa6e087202ec96c479ee2c93b71e -p 420a3454070a1767c3fe7107f9dc753d8ff3722c
f77f5c2466a3f8674d3ec8785b13a910d32e5a75

Вот, таким вот макаром были рождены эти два брата. Для того, чтобы отобразить их отношения, обычного текстового формата недостаточно. Поэтому будем использовать графическую программу gitk. В качестве параметров я перечислил SHA1-имена братьев.

$ gitk de10f1828d215892dcebd00c4f7738141bfd0df7 f77f5c2466a3f8674d3ec8785b13a910d32e5a75

Результат работы можно увидеть вот тут:


Как видно, налицо развилочка. В дальнейшем каждая ветвь может получить отдельное развитие.

Ну, в общем, хватит на сегодня.

О таких захватывающих вещах как коммиты-слияния (merges) я, пожалуй, расскажу в следующий раз.

Комментариев нет:

Отправить комментарий