los_t ([info]los_t) wrote,
@ 2008-04-06 17:50:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:git guts

Git Guts (part 7)
Ветки в git - как ветки деревьев, постоянно обновляются и растут. Одно и то же символьное имя ветки (refs/heads/foo) может указывать на разные коммиты в разные моменты времени. В отличие от веток, теги (tags) - специально созданы для неизменяющихся по времени ссылок.


Символьные имена для тегов лежат в .git/refs/tags. Каждому имени тега может соответствовать один объект в базе git. Это может быть любой из ранее перечисленных типов объектов - блобы, деревья, коммиты.
Символьное имя, указывающее на блоб, дерево или коммит, в терминологии git называется легковесным (lightweight) тегом. Легковесный он потому что кроме SHA1-имени, никакой другой информации не записывается.

Такие легковесные теги можно создавать путем записи SHA1-имени объекта в файл в директории .git/refs/tags/<имя тега>.

Настоящие теги - тяжеловесные или аннотированные (annotated), состоят из двух частей. Первая часть - это объект базы git специального типа (tag). В этот объект записываются следующие данные:


  • SHA1 объекта, на который указывает аннотированный тег.

  • Тип этого объекта (blob, tree, commit или tag) (да, бывают теги указывающие на теги!)

  • Символьное имя тега

  • Дата и время создания тега

  • Имя и e-mail создателя тега (в таком же формате как имя автора коммита)

  • Кусок произвольных данных на усмотрение создателя тега



После чего объект-тег записывается в базу git, и в .git/refs/tags/<имя тега> пишется SHA1 объекта-тега.

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

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

Ладно, хватит теории, давайте перейдем к практике.

Обычные, легковесные теги, как я уже говорил раньше, можно создавать просто записывая SHA1-имя объекта в файл в директории refs/tags/.

Однако правильней создавать их через утилиту git-tag <имя тега> [<имя объекта>]

Если имя объекта не указывать, то по умолчанию тег будет указывать на тот же коммит, на который указывает ссылка HEAD.

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

$ mkdir ~/tmp/gitguts7
$ cd ~/tmp/gitguts7
$ git-init
$ 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 "Testing blobs" > blobtest
$ git-hash-object -w blobtest
717c935c292fee3dca4c2e5f335f27b657895368


Теперь создаем легковесный тег
$ git-tag lighttag 717c935c292fee3dca4c2e5f335f27b657895368
$ cat .git/refs/tags/lighttag
717c935c292fee3dca4c2e5f335f27b657895368


Как видно, по содержанию легковесные теги ничем не отличаются от бранчей - это обычные файлы с SHA1 объекта внутри.
Кстати, команда git-tag без параметров (или git-tag -l) выведет список тегов.

Теперь создадим аннотированный тег (с помощью git-tag -a). Для создания аннотированного тега необходимо указывать практически то же, что и для создания коммита - то есть имя автора тега, дату создания и сообщение. Ну и чтобы получилось одно и то же время, я опять воспользуюсь программой faketime. В отличие от git-commit-tree, команда git-tag более высокоуровневая, и сообщение для тега можно задавать прямо в командной строке, используя параметр -m.

$ faketime -t 200001010000 git-tag -m 'Test annotated tag' -a annotated_tag lighttag

Заметьте, вместо использования SHA1 blob-а, я использовал ранее заданное имя lighttag, которое указывало на этот blob. В этом и весь смысл тегов - давать символьные имена объектам из базы.

Теперь давайте посмотрим, что же получилось в итоге
$ git-rev-parse annotated_tag
40f93cdf3db19ab20109c81f113a7ccb8b921827
$ git-cat-file tag annotated_tag
object 717c935c292fee3dca4c2e5f335f27b657895368
type blob
tag annotated_tag
tagger Git Guts <gitguts@localhost> 946674000 +0300

Test annotated tag

Первая команда (git-rev-parse), позволяет посмотреть, каков SHA1 самого объекта-тега. Вторая команда распечатывает содержимое объекта-тега. В нем можно увидеть SHA1 блоба (первая строчка), тип объекта (вторая строчка), символьное имя (третья строчка), информация об авторе и времени создания тега (четвертая строчка), а ниже - сообщение тега.

Команда создания подписанного тега очень похожа на команду создания обычного тега, просто вместо параметра -a надо передать параметр -s. К сожалению именно эта часть не будет воспроизводиться у читателей, так как у каждого должен быть свой собственный GPG-ключ для подписи. Приведу лишь результаты выполнения команды:

$ faketime -t 200001010000 git-tag -m 'Test annotated tag' -s signed_tag lighttag
$ git-cat-file tag signed_tag
object 717c935c292fee3dca4c2e5f335f27b657895368
type blob
tag signed_tag
tagger Git Guts <gitguts@localhost> 946674000 +0300

Test annotated tag
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEABECAAYFAkf47VMACgkQ8SRmhxtswwQ53wCdHIGaU1ulxud4cUxWVp2pjU1d
358AnAu0Xlti6ZhCSfp9/YToFd//ipcS
=BQ9s
-----END PGP SIGNATURE-----

Как видно, тут к сообщению добавилась подпись, созданная при помощи моего ключа.

Проверить, каким ключом был подписан коммит, можно с помощью git-tag -v
$ git-tag -v signed_tag
object 717c935c292fee3dca4c2e5f335f27b657895368
type blob
tag signed_tag
tagger Git Guts <gitguts@localhost> 946674000 +0300

Test annotated tag
gpg: Подпись создана Вск 06 Апр 2008 19:33:39 MSD ключом DSA с ID 1B6CC304
gpg: Действительная подпись от "Damir Shayhutdinov <damir@altlinux.ru>"

Вот так!

Удалять теги можно с помощью git-tag -d, это я оставляю на самостоятельную работу.


Что-то я сам не ожидал что получится так много, поэтому описание синтаксиса ссылок git-rev-parse оставлю на потом.



(Post a new comment)


[info]levgem
2008-04-28 11:53 am UTC (link)
восторженная публика ждет продолжения!

(Reply to this)


[info]mblsha
2008-04-29 05:01 am UTC (link)
Восторженная публика ждет продолжения, а также протэггивания этого поста ;-)

(Reply to this) (Thread)

git trouble
(Anonymous)
2008-06-02 08:34 pm UTC (link)
Сорри за не в тему, но хочется спросить "умного человека", по вопросам GIT'a.
Как-то пытаюсь использовать GIT, но пока что-то не очень.
(вообще, на работе пользуюсь ClearCase; полгода назад интересовался SVN, но немного подумав - он мне не понравился).

Команды в консоли - это конечно хорошо, но работать с ветками да и бытовуху не очень то приятно делать в консоли (особенно когда всё время работаешь в IDE - Eclipce на работе с плагином ClearCase, Code::Blocks дома без плагинов для контроля версий пока увы...). За это git'у пока тройка (НЕ самому git'у конечно - он же и НЕ должен предоставлять GUI, это не его задача, но GUI пока люди не написали хороший). Это так, на первое грустное..

А вот сама проблема - никак не могу понять как делать commit'ы, мёржи. Во-первых, если git "не осиливает" скрещивание (при конкурентном изменении), то он тупо отказывается это делать. И возникает вопрос - он вообще создаёт какую-то промежуточную картину, чтобы "умный" GUI мог её обработать и предложить программисту интерактивно осуществить мёрж (то есть, по сути разрешить конфликты, как в коммерческих системах контроля версий)?
Но это не страшно - немного (хотя скорее много) помучится и самому можно создать свою новую версию и за commit'ить...
Страшно другое - я попытался создать репозиторий для своего проекта (до этого просто баловался - пробовал что может git и гуи к нему). Я создал репозиторий git-init в пустой папке. Потом переписал туда исходники и создал отправную точку-версию (добавил файлы в репозиторий).
Вообще говоря, работаю под Ubuntu 8.04 amd64. Но поскольку проект мультиплатформенный, и Linux соседствует с Win'усами, то проект находится на NTFS-разделе (ибо работать с NTFS под линухом гораздо проще чем с ext3 под Win). Отсюда первый вопрос - могут ли быть проблемы git'a из-за файловой системы (хотя вроде не должно) и вообще - есть ли какая-то привязка к "текущему месту жительства" репозитория на диске, т.е. можно ли папку спокойно переносить?
Спрашиваю это потому, что после написания кода (большие изменения - много изменилось файлов, много новых, но есть и не изменившиеся), я попытался всё это закоммитить, а git мне какую-то фигню написал, мол ошибка, не хочу, и все изменения выкидывает и на многих файлах пишет какую-то ересть насчёт white-spaces (на пробах он тоже такие предупреждения писал, но коммитил нормально, хотя мне совершенно не понятно к чему он это пишет). И не предложил мне ничего, что можно с этим сделать (мол убейся об стену).
Когда я попробавал commit с force, то всё разумеется улетело в репозитарий и появилась новая глобальная версия, НО все файлы стали как новые, а предыдущая глобальная версия - как-будто к ней вообще никаких файлов не привязано...
Вот...

kirill@sview.ru

(Reply to this) (Parent)(Thread)

Re: git trouble
[info]los_t
2008-06-03 07:28 am UTC (link)
GUI для GIT есть, называется gitk :)

>И возникает вопрос - он вообще создаёт какую-то промежуточную картину, чтобы "умный" GUI мог её обработать и предложить программисту интерактивно осуществить мёрж (то есть, по сути разрешить конфликты, как в коммерческих системах контроля версий)?

В рабочей копии, в том файле в котором замечен конфликт, создаются т.н. конфликтные маркеры, как в CVS/SVN. Достаточно открыть файл в текстовом редакторе и поискать >>>>> и <<<<<
Этого достаточно, никакой GUI тут не нужен, кроме GUI текстового редактора ну или чем вы там пользуетесь.

> Отсюда первый вопрос - могут ли быть проблемы git'a из-за файловой системы (хотя вроде не должно) и вообще - есть ли какая-то привязка к "текущему месту жительства" репозитория на диске, т.е. можно ли папку спокойно переносить?
У файловой системы могут быть проблемы, если она нечувствительна к регистру имени файлов. То есть если в репозитарии есть два файла README и ReadMe, то для юниксовых ФС это будет два разных файла, а для VFAT - один и тот же. Насчет NTFS не знаю точно, вроде там тоже может быть такая проблема.

Привязки к текущему месту жительства насколько я знаю нет, можно переносить спокойно.

Я не совсем понимаю что значит "новая глобальная версия" в Вашей терминологии, но думаю это просто коммит. Если вы приведете точное сообщение об ошибках, которое выдавал git - будет легче. Пока это похоже на включеный commit-hook в репозитарии, который запрещает коммитить файлы в которых есть лишние пробелы после конца строки. Тогда этот хук надо просто отключить и все.

(Reply to this) (Parent)(Thread)

Re: git trouble
(Anonymous)
2008-06-03 06:59 pm UTC (link)
>>Пока это похоже на включеный commit-hook в репозитарии, который запрещает коммитить файлы в которых есть лишние пробелы после конца строки. Тогда этот хук надо просто отключить и все.

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

git-fsckdangling blob 1af7c8c79c07388b789ca0f6ef3c21acb7417037
dangling blob 23f74e49b78ac4a1237f9507d779feba8bb21da2
dangling blob 2aeeda05bcf0642f384c36bc60d4719d8f2a1692
dangling blob 5ccf3054948129bb6ad733b670839082741a6fb5
dangling blob 87dd8124964b001307b810e39fdb6e2269753d92
dangling blob b4c51aaf4b371e0086381cd84736413c2507f8e9
dangling blob b6e856f125f843de1d9d18984190d7dd5cc7df61
dangling blob c35d58ffc3edfd007c8fc8808df6d65f35ad6ab9


Это нормально?

(Reply to this) (Parent)(Thread)

Re: git trouble
[info]los_t
2008-06-03 07:30 pm UTC (link)
> Отключил хук и теперь вроде не ругается... А к чем он вообще там? Он по-умолчанию должен быть включен или выключен?
Ну как бы считается что пробелы после конца строки - зло, с которым стоит бороться. Сделано это для примера, чтобы показать как можно использовать хуки в гите. В принципе это надо выключать, если не нравится.

dangling blob - это вполне может быть результат незакоммиченных файлов в индексе например. Или результат переписывания старого коммита новым. В общем, git-gc --prune их может удалить.

(Reply to this) (Parent)

Re: git trouble
[info]shep256
2008-06-11 09:52 am UTC (link)
У NTFS есть возможность включить регистрочувствительность. По-умолчанию она отключена, увы.

(Reply to this) (Parent)

Re: git trouble
[info]shep256
2008-06-11 09:58 am UTC (link)
Возможно, всё дело в переводах строк. Не факт, но может быть.

Похожая проблема была решена в вебсервере nginx.

Под винду нативной компиляции я не видел, есть cygwin порт. И, соответственно, важно как git открывает файлы. Если не в бинарном режиме, то на винде и на юнихах будет из файликов читаться разное содержимое. Потому что на винде переводы строк \r\n, а на линухе \n.

Возможно, проблема решится, если cygwin настроить на CRLF переводы строк (по-умолчанию он устанавливается с LF (Unix style). Скорее, проблема решится, если git ковырнуть, чтоб он файлики все открывал в бинарном режиме и пересобрать.

(Reply to this) (Parent)

Простите за тупость.
(Anonymous)
2008-09-04 03:37 pm UTC (link)
Привык думать что в репозитарии хранят только diff от начального дерева. Долго читал и перечитывал ... не нашел. Наверное туп, извините, но неужели в репозитарии git хранятся ВСЕ версии одного и того же файла?

(Reply to this) (Thread)

Re: Простите за тупость.
[info]los_t
2008-09-04 05:11 pm UTC (link)
В принципе да, только там используется delta-сжатие, что в общем-то аналогично diff-у, только может применяться не только к истории одного и того же файла, но и между файлами, включая копирования и переименования.

Цитата из man git-pack-objects:
In a packed archive, an object is either stored as a compressed whole, or as a difference from some other
object. The latter is often called a delta.

(Reply to this) (Parent)(Thread)

Re: Простите за тупость.
(Anonymous)
2008-09-05 06:54 am UTC (link)
Да, но ни в одном из Ваших примеров я не видел \Delta сжатия. Простите, наверное я что-то пропустил.....
Собственно не в первом blob-e файла уже должна сидеть \Delta от первого или дельты сохраняются не в blob-ах?
Насамом деле ОЧЕНЬ хочется разобраться, т.к. есть крайне нужное для нашей науки применение. Но увы, пока от чтения user-manual и Вашей серии в голове полная каша.... Не понятно что для чего нужно и как это выглядит в случае линейной истории. Слава богу с ветками, благодаря вам стало намного понятней! Спасибо.

(Reply to this) (Parent)(Thread)

Re: Простите за тупость.
[info]los_t
2008-09-05 07:32 am UTC (link)
Все просто: до вызова git-repack (или включающего его git-gc) блобы действительно хранятся отдельно. Каждый объект (блоб, дерево, коммит, тег) хранится в сжатом виде (сжатие zlib), то есть .git/objects занимает столько же, сколько занимает тарбол tar.gz всех объектов в репозитарии.

Delta-сжатие появляется после вызова git-repack или git-gc. Во время этого процесса все неупакованные объекты объединяются в .pack - файлы, где помимо обычного сжатия, применяется еще дельта-сжатие.

У дельта-сжатия два параметра - окно (window) и глубина (depth).
Чтобы применить дельта-сжатие, git-pack-objects (вызываемый из git-repack) сравнивает куски объектов, применяет к ним дельта-сжатие (то есть образно говоря дифф) и смотрит, получилось ли сэкономить на дельта-сжатии место. Если получилось - то в архив кладется первый объект и дифф до второго. При этом также используется сортировка по типу (вряд ли блоб будет давать хорошую дельту относительно коммита или дерева), затем по размеру (и опционально по имени объекта). Получается список объектов. Для каждого объекта в этом списке проверяются на "дельта-совместимость" ближайшие в списке объекты, находящиеся в рамках "окна" (window). По умолчанию на совместимость проверяется 10 объектов, но этот параметр можно увеличить. По результатам проверки выбирается объект с минимальной дельтой (это может быть в принципе объект, который также является дельтой третьего объекта - в таком случае говорят, что тут глубина дельты равна 2).

Также по умолчанию глубина дельта-сжатия - 50, то есть ориентировочно 50 версий одного и того же файла хранятся в виде дельт относительно друг друга, а пятьдесят первая - в виде полной версии файла. Далее следующие 50 версий этого файла будут опять в виде дельт. Это нужно для ускорения получения блобов - иначе при допустим 1000 изменениях в одном файле последнюю версию надо будет получать, доставая первую и применяя к ней 1000 дельт, а это долго. 50 дельт считается разумным умолчанием. В этом случае достается 950-ая версия и к ней применяется 50 дельт, что в 20 раз быстрее.

Варьируя параметры --window и --depth у git-repack, можно находить подходящий компромисс между временем упаковки и распаковки объекта и занимаемым им местом. При этом параметр --window напрямую влияет на время упаковки (работы git-repack), а --depth - на среднее время распаковки объекта.

(Reply to this) (Parent)(Thread)

Re: Простите за тупость.
[info]nashev
2008-11-25 06:56 pm UTC (link)
то есть, в норме - получается, сыпем при работе в папку .git файлики-объекты, и время от времени (когда захочется) вызываем упаковку насыпанного в очередной .pack?

А git тем временем продолжает прозрачно (только чуть медленнее) работать как с россыпью новых файликов, так и с этими пакетами, как будто все файлики по-прежнему рассыпаны, и вовсе не упаковывались?

(Reply to this) (Parent)(Thread)

Re: Простите за тупость.
[info]los_t
2008-11-26 04:32 am UTC (link)
Да, именно так. Плюс при передаче данных по сети (git push, git pull) тоже передаются пакеты, а не отдельные файлы.

(Reply to this) (Parent)


[info]zhabodav
2009-02-21 01:18 pm UTC (link)
Спасибо за толковое разъяснение, очень помогло. Пришел к пониманию, что git в первую очередь не программа, а архитектура. Причем архитектура, удобная не только для ведения репозитория кода. Например как хранилище rdf (или схожих триплетных данных) - сдается мне, тоже будет полезна.
З.Ы. Есть скромное мнение, что в этом цикле статей не хватает разъяснений по поводу механики работы refs/remotes

(Reply to this)


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