Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Магия GIT.pdf
Скачиваний:
43
Добавлен:
15.03.2015
Размер:
316.64 Кб
Скачать

Магия Git

33 / 41

chains (цепи хешей). Позднее мы увидим, как Git использует их для эффективного обеспечения целостности данных.

Короче говоря, Git хранит ваши данные в подкаталоге ".git/objects", где вместо обычных файлов, вы увидите только ID. С помощью идентификаторов как имен файлов, а также нескольких лок-файлов и трюков с временем создания файлов, Git преобразует любую скромную файловую систему в эффективную и надежную базу данных.

8.3 Интеллект

Каким образом Git знает, что вы переименовали файл, даже если вы никогда не упоминается тот факт явно? Конечно, вы можете запустить git mv, но это точно так же, как git rm и после git add.

Git эвристически находит переименованные файлы и копирует их в последующие версии. В самом деле, он может обнаружить, что куски кода были перемещены или скопированы между файлами! Хотя она не может охватить все случаи, это достойная работа, и эта функция всегда улучшается. Если это не работает, попробуйте включить опцию обнаружения копирования и рассмотреть вопрос апгрейда.

8.4 Индексация

Для каждого отслеживаемого файла, Git записывает информацию, такую как размер, время создания и время последнего изменения в файл, известный как "индекс". Чтобы определить, что файл был изменен, Git сравнивает его текущее состояние с тем, что сохранено в индексе. Если они совпадают, то Git может пропустить перечитывание это файла.

Поскольку чтение этой информации значительно быстрее, чем чтение всего файла, то если вы редактировали только несколько файлов, Git может обновить свой индекс почти мгновенно.

8.5 Голые репозитории

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

Большинство команд Git рассчитывают что индекс Git находится в каталоге .git, и не смогут работать на этих голых хранилищах. Исправить это можно, установив переменную окружения GIT_DIR в значение, равное пути к репозиторию, или запустить Git в этом каталоге с опцией

--bare.

8.6 Происхождение Git

Этот http://lkml.org/lkml/2005/4/6/121 [пост] в Linux Kernel Mailing List описывает цепь событий,

которые привели к появлению Git. Весь этот трейд - увлекательные археологические раскопки для историков Git.

8.7 База данных объектов

Вот как писать Git-подобной операционной системы с нуля в течение нескольких часов.

Магия Git

34 / 41

8.7.1 Blobs

Первый волшебный трюк. Выберите имя файла, любое имя файла. В пустой директории:

$ echo sweet > YOUR_FILENAME $ git init

$ git add .

$ find .git/objects -type f

Вы увидите .git/objects/aa/823728ea7d592acc69b36875a482cdf3fd5c8d.

Откуда я знаю это, не зная имени файла? Это потому, что SHA1 хэш строки:

"blob" SP "6" NUL "sweet" LF

является aa823728ea7d592acc69b36875a482cdf3fd5c8d, где SP это пробел, NUL является нуле-

вым байтом и LF переводом строки. Вы можете проверить это, напечатав:

$ echo "blob 6"$'\001'"sweet" | tr '\001' '\000' | sha1sum

Кстати, это написано с учетом особенностей оболочки Bash, другие оболочки возможно способны обработать NUL в командной строке, что исключает необходимость использовать костыль с tr.

Git является контент-адресуемым: файлы хранятся в независимости от их имени, а по хэшу содержимого, которое мы называем BLOB объект. Мы можем думать о хеше как о уникальном идентификаторе для содержимого файла, так что в некотором смысле мы обращаемся к файлам по их содержимому. Начальный "blob 6" является лишь заголовком, состоящий из типа объекта и его длины в байтах; она упрощает внутренний учет.

Таким образом, я могу легко предсказать, что вы увидите. Имя файла не имеет никакого отношения: только данные внутри используется для построения BLOB объекта.

Вам может быть интересно, что происходит с идентичными файлами. Попробуйте добавить копии с любыми именами файлов вообще. Содержание .git/objects останется тем-же независимо от того, сколько копий вы добавите. Git только хранит данные один раз.

Кстати, файлы в директории .git/objects сжимаются с Zlib поэтому вы не сможете просмотреть их непосредственно. Пропустите их через фильтр http://www.zlib.net/zpipe.c [zpipe-D], или введите:

$ git cat-file -p aa823728ea7d592acc69b36875a482cdf3fd5c8d

который просто выведет данный объект.

8.7.2 Деревья

Но где же имена файлов? Они должны храниться где-то на определенном этапе. Git получает информацию об имени во время коммита:

$

git commit # Type some

message.

$

find .git/objects -type

f

Теперь вы должны увидеть 3 объекта. На этот раз я не могу сказать вам, какие 2 новые файлы, так как это частично зависит от выбранного имени файла. Допустим вы назвали его "rose". Если это не так, то вы можете переписать историю, чтобы она выглядела как будто вы это сделали:

Магия Git

35 / 41

$ git filter-branch --tree-filter 'mv YOUR_FILENAME rose' $ find .git/objects -type f

Теперь вы должны увидеть файл .git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9,

потому что это SHA1 хэш его содержимого:

"tree" SP "32" NUL "100644 rose" NUL 0xaa823728ea7d592acc69b36875a482cdf3fd5c8d

Проверьте - этот файл действительно содержит указанную выше строку - наберите:

$ echo 05b217bb859794d08bb9e4f7f04cbda4b207fbe9 | git cat-file --batch

С zpipe легко проверить хеш:

$ zpipe -d < .git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9 | sha1sum

Проверка хеша сложнее чем через CAT-файла, поскольку его вывод содержит больше, чем сырой несжатый объектный файл.

Этот файл является объектом tree: список цепочек, состоящих из типа файла, имени файла, и хэша. В нашем примере это тип файла "100644", это означает что "rose", является обычным файлом и хэш BLOB объект, в котором находится содержимое "rose". Другие возможные типы файлов - исполняемые файлы, символические ссылки или каталоги. В последнем случае, хэш указывает на дереве объектов.

Если вы запускали filter-branch, у вас будут старые объекты которые вам больше не нужны. Хотя они будут автоматически выброшены сразу после истечения льготного периода, мы удалим их сейчас, чтобы наш игрушечный пример было легче исследовать:

$ rm -r .git/refs/original

$ git reflog expire --expire=now --all $ git prune

Для реальных проектов, обычно вы должна избегать использовать такие команды, как эта, так как вы разрушаете резервные копии. Если вы хотите чистое хранилище, то обычно лучше сделать новый клон. Кроме того, будьте внимательны при непосредственном манипулировании .gi- t: Что делать, если другая команда Git будет запущена в то же время, или внезапного произойдет отключение питания? В общем случае, ссылки должны быть удалены с помощью git update-ref -d, хотя обычно удалить refs/original вручную безопасно.

8.7.3 Коммиты

Мы объяснили 2 из 3 объектов. Третий объект - коммит. Его содержимое зависит от сообщения коммита, а также от даты и времени его создания. Для демонстрации того, что мы здесь имеем, мы должны настроить Git немного:

$

git commit --amend -m Shakespeare # Change the commit message.

$

git filter-branch --env-filter 'export

 

GIT_AUTHOR_DATE="Fri 13 Feb 2009 15:31:30 -0800"

 

GIT_AUTHOR_NAME="Alice"

 

GIT_AUTHOR_EMAIL="alice@example.com"

 

GIT_COMMITTER_DATE="Fri, 13 Feb 2009 15:31:30 -0800"

 

GIT_COMMITTER_NAME="Bob"

 

GIT_COMMITTER_EMAIL="bob@example.com"' # Rig timestamps and authors.

$

find .git/objects -type f

 

 

Магия Git

36 / 41

Теперь вы должны увидеть .git/objects/49/993fe130c4b3bf24857a15d7969c396b7bc187 кото-

рый является SHA1 хэшем его содержание:

"commit 158" NUL

"tree 05b217bb859794d08bb9e4f7f04cbda4b207fbe9" LF "author Alice <alice@example.com> 1234567890 -0800" LF "committer Bob <bob@example.com> 1234567890 -0800" LF LF

"Shakespeare" LF

Как и раньше, вы можете запустить zpipe или cat-file, чтобы увидить это самостоятельно.

Это первый коммит, и поэтому нет родительского коммита, но последующие коммиты всегда будет содержать хотя бы одну строку идентифицирующую родительский коммит.

8.7.4 Неотличимо от магии

Там мало сказано. Мы только что открыли секрет мощи Git. Это кажется слишком простым: похоже, что вы могли бы смешать вместе несколько скриптов оболочки и добавить немного кода на C, сделанного в считанные часы. По сути, это точное описание ранних версий Git. Тем не менее, помимо гениальных трюков упаковки, чтобы сэкономить место, и трюков индексации, чтобы сэкономить время, мы теперь знаем, как ловко Git преображает файловую систему в базу данных, идеально подходящую для контроля версий.

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

Мы наносим поражение даже самым хитрым противникам. Пусть кто-то попытается тайно изменить содержимое файла в древней версии проекта. Чтобы сохранить объектную базу данных согласованной, они также должны изменить хеш соответствующего объекта BLOB, поскольку это теперь другая последовательность байтов. Это означает, что нужно поменять хэш всех деревьев, содержащих ссылки на объект этого файла, что в свою очередь изменит хэши коммитов всех объектов с участием таких деревьев, в дополнение к хэшам всех потомков этих коммитов. Это означает, хэш официальной головной ревизии будет отличаться от хеша в этом плохом хранилище. По следам несовпадения хэшей мы можем локализовать изуродованный файл, а также коммит, где он впервые был поврежден.

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

Как насчет знаменитых черт Git? Создание ветки? Слияние? Теги? Более подробно. Текущая HEAD хранится в файле .git/HEAD, который содержит хэш объекта фиксации. Хэш обновляется во время коммита, а также при выполнении многих других команд. Ветки почти одинаковы: они представляют собой файлы в .git/refs/heads. Тэги тоже: они живут в .git/refs/tags, но они обновляться различными наборами команд.