Протоколы передачи

Здесь мы узнаем как клиенты и серверы общаются друг с другом и обмениваются данными Git между собой.

Извлечение данных через HTTP

Извлечение через http/s URL заставит Git использовать немного глупый протокол. В этом случае, вся логика ложится на сторону клиента. Сервер не требует специальной настройки - любой статический вебсервер подойдет для этой работы если директори git которую вы извлекает доступна вебсерверу.

Чтобы это работало, вам нужно выполнять единственную команду на репозитории сервера каждый раз если что то обновилось, команда выглядит след.образом git update-server-info, которая обновляет файлы objects/info/packs и info/refs чтобы составить список какие ссылки и пакфайлы досупны, так как вы не можете изменить этот список через http. Когда эта команда выполняется, файл objects/info/packs выглядит приблизительно след.образом:

P pack-ce2bd34abc3d8ebc5922dc81b2e1f30bf17c10cc.pack
P pack-7ad5f5d05f5e20025898c95296fe4b9c861246d8.pack

Так что если извлечение не может найти свободный(loosy) файл, то оно может попробовать эти пакфайлы. Файл info/refs приблизительно будет выглядеть след.образом:

184063c9b594f8968d61a686b2f6052779551613    refs/heads/development
32aae7aef7a412d62192f710f2130302997ec883    refs/heads/master

Потом когда вы извлекаете из этого репозитория, то процесс начнет с этих refs и пройдет объекты коммит до тех пор пока клиет не получит все объекты которые ему нужны.

Например, если вы попросите извлечь ветку master, то видно что ветка master указывает на 32aae7ae а ваша ветка master указывает на ab04d88, так что вам нужно 32aae7ae. Вы извлекаете этот объект

CONNECT http://myserver.com
GET /git/myproject.git/objects/32/aae7aef7a412d62192f710f2130302997ec883 - 200

И это выглядит след.образом:

tree aa176fb83a47d00386be237b450fb9dfb5be251a
parent bd71cad2d597d0f1827d4a3f67bb96a646f02889
author Scott Chacon <schacon@gmail.com> 1220463037 -0700
committer Scott Chacon <schacon@gmail.com> 1220463037 -0700

added chapters on private repo setup, scm migration, raw git

Теперь это извлекает дерево aa176fb8:

GET /git/myproject.git/objects/aa/176fb83a47d00386be237b450fb9dfb5be251a - 200

которе выглядет след.образом:

100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3    COPYING
100644 blob 97b51a6d3685b093cfb345c9e79516e5099a13fb    README
100644 blob 9d1b23b8660817e4a74006f15fae86e2a508c573    Rakefile

И теперь это извлекает эти объекты:

GET /git/myproject.git/objects/6f/f87c4664981e4397625791c8ea3bbb5f2279a3 - 200
GET /git/myproject.git/objects/97/b51a6d3685b093cfb345c9e79516e5099a13fb - 200
GET /git/myproject.git/objects/9d/1b23b8660817e4a74006f15fae86e2a508c573 - 200

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

GET /git/myproject.git/objects/bd/71cad2d597d0f1827d4a3f67bb96a646f02889 - 200

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

tree b4cc00cf8546edd4fcf29defc3aec14de53e6cf8
parent ab04d884140f7b0cf8bbf86d6883869f16a46f65
author Scott Chacon <schacon@gmail.com> 1220421161 -0700
committer Scott Chacon <schacon@gmail.com> 1220421161 -0700

added chapters on the packfile and how git stores objects

и можем видеть что родитель, ab04d88 это куда наша ветка master в данный момент указывает. Теперь, мы рекурсивно извлекаем это дерево и затем останавливаемся, так как мы знаем что у нас есть все что идет до этой точки. Вы можете заставить Git дважды проверить, то что у нас уже есть все, с помощью параметра '--recover'. Просмотрите документацию git http-fetch чтобы получить больше подробностей.

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

Это важно если вы используете git сервер который обслуживает репозитории этим способом чтобы реализовать хуки post-recieve которые запускаются командой 'git update-server-info' каждый раз или произойдет путаница.

Извлечение данных с помощью выгрузки пакетов

В более умных протоколах, извлечение объектов более эффективно. Сокет открыт, или через ssh или другой порт 9418 (в случае протокола git://), и команда git fetch-pack на клиенте начнет сообщаться с форком процесса git upload-pack на сервере..

Затем сервер сообщит клиенту которое SHA значение он имеет для каждой ref, и клиент определит что ему нужно и ответит списком SHA значений которые ему нужныи которые у него уже есть.

В этот момент, сервер сгенерирует пакфайл со всеми объектами которые нужны клиенту и передаст их клиенту.

Давайте взглянем на пример.

Клиент соединяется и посылает заголовок запроса. Команда клон

$ git clone git://myserver.com/project.git

производит следующий запрос:

0032git-upload-pack /project.git\000host=myserver.com\000

Первые 4 байта содержат 16ое длину строки (включая 4 байта длины строки и символ окончания строки если таковой имеется). Следующее это команды и их аргументы. Затем идет нулевой байт и затем данные о хосте. Запрос заканчивается нулевым байтом.

Запрос обрабатывается и конвертируется в вызов git-upload-pack:

$ git-upload-pack /path/to/repos/project.git

Это немедленно возвращает информацию репозитория:

007c74730d410fcb6603ace96f1dc55ea6196122532d HEAD\000multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress
003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug
003d5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/dist
003e7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 refs/heads/local
003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master
0000

Каждая строка начинается с 4 байт строки длины объявления в hex. Эта часть завершается строкой с длиной объявления 0000.

Это отсылается назад клиенту. Клиент отвечает другим запросом:

0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack side-band-64k ofs-delta
0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe
0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a
0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01
0032want 74730d410fcb6603ace96f1dc55ea6196122532d
00000009done

Это оправлено чтобы открыть процесс git-upload-pack который затем создат поток и вернет его назад как заключительный ответ:

"0008NAK\n"
"0023\002Counting objects: 2797, done.\n"
"002b\002Compressing objects:   0% (1/1177)   \r"
"002c\002Compressing objects:   1% (12/1177)   \r"
"002c\002Compressing objects:   2% (24/1177)   \r"
"002c\002Compressing objects:   3% (36/1177)   \r"
"002c\002Compressing objects:   4% (48/1177)   \r"
"002c\002Compressing objects:   5% (59/1177)   \r"
"002c\002Compressing objects:   6% (71/1177)   \r"
"0053\002Compressing objects:   7% (83/1177)   \rCompressing objects:   8% (95/1177)   \r"
...
"005b\002Compressing objects: 100% (1177/1177)   \rCompressing objects: 100% (1177/1177), done.\n"
"2004\001PACK\000\000\000\002\000\000\n\355\225\017x\234\235\216K\n\302"...
"2005\001\360\204{\225\376\330\345]z2673"...
...
"0037\002Total 2797 (delta 1799), reused 2360 (delta 1529)\n"
...
"<\276\255L\273s\005\001w0006\001[0000"

Просмотрите предыдущую главу Пакфайл чтобы получить описание формата данных пакфайла в ответе.

Выполнение push

Выполнение push через git и ssh протоколы похоже, но проще. По существу происходит то что клиент запрашивает экземпляр receive-pack, который запускается если клиент имеет доступ, затем сервер возвращает все sha значения заголовков ссылок, которые у него есть опять и клиент генерирует пакфайл всего что требуется серверу (обычно только если то что на сервере это прямой предок того для чего и выполняется push) и посылает этот пакфайл в исходящий поток, где сервер или сохраняет его на диске и строит индекс для него, или распаковывает его (если там не много объектов)

Это весь процесс выполняется с помощью команды git send-pack на клиенте, которая вызывается git push и git receive-packкомандой на стороне сервера, которая вызывается процессом соединения ssh или демоном git (если это открытый push сервер).



github logo