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

Git. part 6

Взято здесь

SHA1-имена объектов как уникальные идентификаторы - это конечно удобно. Для роботов. Люди как-то привыкли называть друг друга по коротким именам, а не по кодам ДНК.

Символьные имена объектов в git называются ссылка (references), и хранятся в каталоге .git/refs.

Делятся они на три типа:

1. Теги (tags) - символьные имена любых объектов из базы, которые не меняются со временем. Расположены в .git/refs/tags/

2. Ветки (heads, branches) - символьные имена объектов-коммитов, которые меняются при добавлении нового коммита в цепочку. Расположены в .git/refs/heads/

3. Удаленные ветки (remotes) - ветки специального вида, которые предназначены для слежения за ветками (heads) в других репозитариях. Лежат в .git/refs/remotes/

Кроме этого, есть несколько специальных ссылок, которые по историческим соображениям лежат вне каталога .git/refs и их названия пишутся В РЕГИСТРЕ БЛОНДИНОК. Из всех БЛОНДИНОЧНЫХ ссылок для пользователей наиболее важными являются HEAD, ORIG_HEAD и MERGE_HEAD.

HEAD - это особая ссылка, она показывает на коммит, который соответствует рабочей копии. Если быть точным, это не просто ссылка на коммит, это ссылка на "текущую ветку".

Хватит теории, пора иллюстрировать. Создаем простой репозитарий и попробуем те самые высокоуровневые инструменты, которыми раньше не пользовались.

$ mkdir ~/tmp/gitguts6
$ cd ~/tmp/gitguts6
$ git-init
Initialized empty Git repository in .git/
$ git-mktree < /dev/null 4b825dc642cb6eb9a060e54bf8d69288fbee4904 $ TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 $ 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 "Первый коммит" | iconv -t utf-8 | faketime -t 200001010000 git-commit-tree $TREE e678a27ffe7b84211f09b0e397b1c6e287aee392

Итак, был создан новый коммит с пустым деревом. Теперь создадим символьное имя для этого коммита - пусть это будет имя "refs/heads/master". Для этого надо лишь создать обычный файл .git/refs/heads/master и записать в него SHA1-имя коммита. Примерно так:

$ echo e678a27ffe7b84211f09b0e397b1c6e287aee392 > .git/refs/heads/master
$ ln -sf refs/heads/master .git/HEAD

Как видно из примера, я создал ссылку-ветку refs/heads/master, после чего сделал интересную операцию - символическую ссылку .git/HEAD на эту ветку.

Новую созданную ветку может показать команда git-branch:

$ git-branch
* master

Звездочка около master означает что сейчас ссылка HEAD указывает именно на эту ветку.

Чтобы увидеть, как сменой ссылки HEAD git может "переключаться" между ветками, создадим вторую ветку, указывающую на тот же коммит:

$ echo e678a27ffe7b84211f09b0e397b1c6e287aee392 > .git/refs/heads/other
$ git-branch
* master
other
$ ln -sf refs/heads/other .git/HEAD
$ git-branch
master
* other

Мы видим как простым переставлением символьной ссылки .git/HEAD выбирается "текущая" ветка.

После того, как было создано символьное имя объекта, можно получить по нему SHA1-имя с помощью команды git-rev-parse:

$ git-rev-parse refs/heads/master
e678a27ffe7b84211f09b0e397b1c6e287aee392
$ git-rev-parse refs/heads/other
e678a27ffe7b84211f09b0e397b1c6e287aee392

Вместо refs/heads/master можно использовать heads/master или master. Также работают БЛОНДИНИСТЫЕ ссылки типа HEAD:

$ git-rev-parse HEAD
e678a27ffe7b84211f09b0e397b1c6e287aee392

Используя git-rev-parse и задание ссылок, можно произвести и простую операцию "коммит в ветку", с которой обычно начинается знакомство с git. Вот эта операция, пошагово:
$ PARENT=`git-rev-parse HEAD` # SHA1 текущего коммита
$ echo "Коммит в ветку other" | iconv -t utf-8 | faketime -t 200001010100 git-commit-tree $TREE -p $PARENT # создаем новый коммит, используя в качестве родителя коммит HEAD
283f22289f768361b854a78f1764dc7f1bd9b822
$ echo 283f22289f768361b854a78f1764dc7f1bd9b822 > .git/HEAD # переставляем ссылку HEAD на новый коммит
$ git-branch # смотрим, по прежнему ли мы на ветке other?
master
* other

Заметили магию? Из-за того, что .git/HEAD - символическая ссылка на .git/refs/heads/other, запись SHA1 нового коммита в .git/HEAD на самом деле записывает новый коммит в refs/heads/other, затирая предыдущее значение. Теперь ссылка refs/heads/other указывает на новый коммит.

Вот схема:
Было:
HEAD -> refs/heads/other -> старый коммит
Стало:
HEAD -> refs/heads/other -> новый коммит -> старый коммит

То, что раньше пришлось делать вручную - теперь делается через механизм веток и HEAD! Для того, чтобы добавить новый коммит в ветку - надо просто повторить вышеуказанную процедуру. Можно даже сделать это в одну строчку, и при этом совсем избежать указаний SHA1.

$ echo "Еще один коммит в ветку other" | iconv -t utf-8 | faketime -t 200001010200 git-commit-tree $TREE -p `git-rev-parse HEAD` > .git/HEAD # магическая строчка, коммитящая в ветку
$ PAGER=cat git-log --pretty=oneline # git-log без указания коммита показывает историю HEAD
afd309cb9fe66dc314ed54c272a2d26a1b7a01be Еще один коммит в ветку other
283f22289f768361b854a78f1764dc7f1bd9b822 Коммит в ветку other
e678a27ffe7b84211f09b0e397b1c6e287aee392 Первый коммит

Вот так - в ветке other теперь было создано уже три коммита.

Если же теперь переставить ссылку HEAD на refs/heads/master, точно такой же процедурой можно добавлять коммиты в ветку master:

$ ln -sf refs/heads/master .git/HEAD
$ git-branch
* master
other
$ echo "Теперь коммит в ветку master" | iconv -t utf-8 | faketime -t 200001010300 git-commit-tree $TREE -p `git-rev-parse HEAD` > .git/HEAD
$ PAGER=cat git-log --pretty=oneline
22339820c0dd6758be9cd940db0306d4020f7c9f Теперь коммит в ветку master
e678a27ffe7b84211f09b0e397b1c6e287aee392 Первый коммит


Ну и под занавес - посмотрите как эти ветки выглядят в gitk.

$ gitk --all

Параметр --all говорит gitk показывать все символьные ссылки, а не только те, которые доступны из HEAD. Поэтому мы увидим все две ветки, которые были созданы:

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

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