Протоколы передачи
Git может передавать данные между репозиториями одним из двух основных способов: через HTTP или через "умные" протоколы для транспортов file://
, ssh://
и git://
. В данном разделе мы кратко рассмотрим, как эти два протокола работают.
Тупой протокол
Git-транспорт, работающий по HTTP, часто называют "тупым" протоколом, потому что для его работы во время передачи данных не требуется исполнения никакого Git-специфичного кода на стороне сервера. Процесс извлечения данных представляет собой последовательность GET-запросов, клиент обращается к стандартной структуре каталогов Git'а. Давайте рассмотрим процесс получения данных по HTTP на примере библиотеки simplegit:
$ git clone http://github.com/schacon/simplegit-progit.git
Первое действие, выполняемое данной командой — загрузка файла info/refs
. Данный файл записывается командой update-server-info
, поэтому для использования HTTP-транспорта необходимо запускать эту команду в перехватчике post-receive
:
=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
Теперь у нас имеется список удалённых веток и их хеши. Далее, нам надо посмотреть, куда ссылается HEAD, чтобы знать на какую версию переключиться после завершения работы команды.
=> GET HEAD
ref: refs/heads/master
Нам надо переключиться на ветку master
после завершения процесса.
На данном этапе можно начать обход дерева. Начальной точкой является объект-коммит ca82a6
, о чём мы узнали из файла info/refs
, и мы начинаем с его загрузки:
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)
Объект получен, он был в рыхлом формате на сервере, и мы получили его по HTTP, используя статический GET-запрос. Теперь можно его разархивировать, отрезать заголовок и посмотреть на его содержимое:
$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700
changed the version number
Далее, необходимо загрузить ещё два объекта: cfda3b
— объект-дерево, который обозначен как содержимое только что загруженного коммита, и 085bb3
— родительский коммит:
=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)
Так, мы получили следующий объект-коммит. Прихватим и наш объект-дерево:
=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)
Ой! Похоже, этого объекта-дерева нет на сервере в рыхлом формате, поэтому мы получили ответ 404. У этого могут быть две причины: или объект в другом репозитории, или в упакованном файле текущего репозитория. Сперва Git проверяет список альтернативных репозиториев:
=> GET objects/info/http-alternates
(empty file)
Если бы этот запрос вернул нам список альтернативных URL, Git обратился бы по ним в поиске "рыхлых" и pack-файлов — это такой механизм, позволяющий не дублировать данные проектам, являющимися форками друг для друга. Так как в данном случае альтернативных адресов нет, объект должен быть в pack-файле. Для того чтобы узнать, какие упакованные файлы есть на сервере, необходимо загрузить файл со списком pack-файлов: objects/info/packs
(который также генерируется update-server-info
):
=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
На сервере имеется только один pack-файл, поэтому объект точно там, но необходимо проверить индексный файл, чтобы в этом убедиться. Если бы на сервере было несколько pack-файлов, загрузив сначала индексы, мы смогли бы определить, в каком именно pack-файле находится нужный нам объект:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)
Теперь, когда мы получили индекс упакованного файла, можно проверить, тут ли наш объект. Это возможно благодаря тому, что в индексе хранятся SHA-1 объектов содержащихся в pack-файле, а также их смещения. Необходимый объект там присутствует, так что продолжим и получим весь pack-файл:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)
Итак, мы получили наш объект-дерево, можно продолжить обход списка коммитов. Все они лежат внутри упакованного файла, который мы только что скачали, так что снова обращаться к серверу не надо. Git извлекает рабочую копию ветки master
, на которую ссылается HEAD.
Полный вывод этого процесса выглядит так:
$ git clone http://github.com/schacon/simplegit-progit.git
Initialized empty Git repository in /private/tmp/simplegit-progit/.git/
got ca82a6dff817ec66f44342007202690a93763949
walk ca82a6dff817ec66f44342007202690a93763949
got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Getting alternates list for http://github.com/schacon/simplegit-progit.git
Getting pack list for http://github.com/schacon/simplegit-progit.git
Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835
Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835
which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf
walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
walk a11bef06a3f659402fe7563abf99ad00de2209e6
Умный протокол
Методика работы HTTP проста, но неэффективна, поэтому чаще используются "умные" протоколы. Эти протоколы обслуживаются процессом на стороне сервера, который учитывает особенности работы Git'а — он считывает локальные данные, выясняет, что есть и чего не хватает на клиенте, и генерирует для него соответствующие данные. Существует два набора процессов передачи данных: процессы для загрузки данных и процессы для скачивания.
Загрузка данных
Для загрузки данных на удалённый сервер используются процессы send-pack
и receive-pack
. Процесс send-pack
запускается на стороне клиента и подключается к receive-pack
на стороне сервера.
Например, выполняется команда git push origin master
и origin
определён как URL использующий протокол SSH. Git запускает процесс send-pack
, который устанавливает соединение с сервером по протоколу SSH. Он пытается запустить команду на удалённом сервере через вызов команды ssh, который выглядит следующим образом:
$ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'"
005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs
003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic
0000
Команда git-receive-pack
тут же посылает в ответ по одной строке на каждую из имеющихся в наличии ссылок — в данном случае только ветку master
и её SHA. Первая строка также содержит список возможностей сервера (здесь это report-status
и delete-refs
).
Каждая строка начинается с 4-байтового шестнадцатеричного значения, содержащего длину оставшейся строки. Первая строка начинается с 005b, это 91 в 16-ричном виде, значит в этой строке ещё 91 байт. Следующая строка начинается с 003e, что означает 62, то есть надо прочитать 62 байта. Далее следует строка 0000, которая означает, что сервер закончил листинг своих ссылок.
Теперь, когда процесс send-pack
выяснил состояние сервера, он определяет коммиты, которые есть локально, но которых нет на сервере. Для каждой ссылки, которая будет обновлена текущей командой push
, процесс send-pack
передаёт процессу receive-pack
эти данные. Например, если мы обновляем ветку master
, и добавляем ветку experiment
, ответ send-pack
будет выглядеть следующим образом:
0085ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status
00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment
0000
Значение SHA-1 из одних нулей означает, что раньше здесь ничего не было — так получилось из-за того, что мы добавили новую ссылку experiment
. Если бы мы удаляли ссылку, было бы на оборот: одни нули были бы справа.
Git отправляет строку для каждой ссылки, для которой производится обновление. В строке содержится старый хеш, новый хеш и имя обновляемой ссылки. Первая строка также содержит возможности клиента. Далее, клиент загружает упакованный файл со всеми объектами, которых ещё нет на сервере. В конце, сервер отвечает статусным сообщением сообщающем об успехе (или ошибке):
000Aunpack ok
Скачивание данных
Если выполняется скачивание данных, используются процессы fetch-pack
и upload-pack
. Клиент запускает процесс fetch-pack
, который подключается к процессу upload-pack
на удалённой машине для определения, какие данные будут переданы.
Существуют разные способы запуска upload-pack
на удалённом репозитории. Можно запустить его по SSH так же, как и receive-pack
. Ещё можно вызвать процесс через Git-демон, по умолчанию принимающий соединения на порте 9418. Процесс fetch-pack
после подключения отправляет демону данные примерно следующего вида:
003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0
Начальные 4 байта задают размер последующих данных, далее следует команда, которую следует запустить, завершаемая нулевым байтом, а потом имя сервера и последний нулевой байт. Git-демон проверяет возможность выполнения команды, а также, что репозиторий существует и имеет необходимые права доступа. Если всё хорошо, демон запускает процесс upload-pack
и передаёт запрос ему.
Если извлечение данных производится по SSH, fetch-pack
выполняет другие действия:
$ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"
В обоих случаях, после того как fetch-pack
подключится, upload-pack
передаст обратно следующее:
0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \
side-band side-band-64k ofs-delta shallow no-progress include-tag
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic
0000
Это очень похоже на ответ receive-pack
, но только возможности другие. Вдобавок upload-pack
отсылает обратно ссылку HEAD, чтобы клиент понимал, на какую ветку переключиться, если выполняется клонирование.
На данном этапе процесс fetch-pack
смотрит на объекты, имеющиеся в наличии, и для недостающих объектов отвечает словом "want" и за ним SHA объекта. Для уже имеющихся объектов процесс отправляет их хеши со словом "have". В конце списка он пишет "done", и это даёт понять процессу upload-pack
, что пора начинать отправлять упакованный файл с необходимыми данными:
0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0000
0009done
Это самый основной случай передачи данных. В более сложных случаях клиент поддерживает функции multi_ack
или side-band
, но этот пример иллюстрирует основные взаимодействия, используемые процессами умного протокола.