Перейти к содержимому

Интеллектуальный HTTP

Pitmaster обменивается данными с серверами git через HTTPS по протоколу smart HTTP. Это основной сетевой транспорт. Для запросов HTTP он использует встроенные потоковые функции PHP (fopen с контекстом потока), поэтому необходимо включить allow_url_fopen.

SmartHttpClient

PHP
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient();
// Or with custom timeout:
$http = new SmartHttpClient(timeout: 60);

По умолчанию тайм-аут равен константе PITMASTER_HTTP_TIMEOUT (целое число) или 30 секундам, если она не определена.

Обнаружение ссылок

PHP
$discovery = $http->discoverRefs('https://github.com/user/repo.git');

foreach ($discovery->refs() as $name => $id) {
    echo "{$id->hex} {$name}\n";
}

// HEAD symref (e.g., 'refs/heads/main')
$defaultBranch = $discovery->headSymref();

// Specific ref
$mainId = $discovery->ref('refs/heads/main');

// Server capabilities (a Capability object, or null)
$caps = $discovery->capabilities();

discoverReceivePackRefs() выполняет то же для git-receive-pack. Запрос обнаружения ссылок отправляется на адрес:

Код
GET <url>/info/refs?service=git-upload-pack

Ответ представляет собой закодированный в pkt-line список ссылок, возможности указаны в первой строке.

Загрузка pack (fetch)

PHP
$response = $http->uploadPack($url, $requestBody);

Отправляет POST-запрос на <url>/git-upload-pack с телом, содержащим согласование want/have. Возвращает исходное тело ответа (строки pkt-line, NAK и данные side-band). UploadPackClient формирует запрос и извлекает pack:

PHP
use Pitmaster\Protocol\UploadPackClient;

$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves);   // ObjectId[] wants and haves

Получение pack (push)

PHP
$response = $http->receivePack($url, $requestBody);

Отправляет POST-запрос на <url>/git-receive-pack с командами обновления ссылок и данными pack.

Кодирование PktLine

Протокол git использует кадрирование pkt-line для всей связи. Перед каждой строкой указывается её общая длина (включая 4-байтовый префикс длины), закодированная четырьмя шестнадцатеричными цифрами.

PHP
use Pitmaster\Protocol\PktLine;

// Encode a single line
$encoded = PktLine::encode("want abc123def456\n");
// "0016want abc123def456\n"

// Flush packet (end of section)
$flush = PktLine::flush();
// "0000"

// Delimiter packet (protocol v2)
$delim = PktLine::delimiter();
// "0001"

// Decode a stream
$lines = PktLine::decode($rawData);
// Each element is: string (data), null (flush), or false (delimiter)

Правила длины

  • Минимальная строка данных: 0005 (полезная нагрузка 1 байт)
  • Максимальная строка данных: fff0 (полезная нагрузка 65516 байт)
  • Пакет сброса: 0000
  • Пакет-разделитель: 0001
  • Завершение ответа: 0002 (протокол v2; обрабатывается в ProtocolV2::decode(), а PktLine::decode() его отклоняет)
PHP
PktLine::MAX_PAYLOAD;  // 65516 (65520 - 4)

Обнаружение ссылок

Класс RefDiscovery разбирает объявление ссылок сервера.

PHP
use Pitmaster\Protocol\RefDiscovery;

$discovery = RefDiscovery::parse($pktLines);

// All refs
$refs = $discovery->refs();
// ['refs/heads/main' => ObjectId, 'refs/tags/v1.0' => ObjectId, ...]

// Single ref
$id = $discovery->ref('refs/heads/main');

// HEAD symbolic ref (from capabilities)
$symref = $discovery->headSymref();
// 'refs/heads/main' or null

// Server capabilities
$caps = $discovery->capabilities();
// Capability object (see below), or null

Формат объявления ссылок

Первая строка объявления содержит возможности:

Код
<hash> HEAD\0<capability list>\n
<hash> refs/heads/main\n
<hash> refs/tags/v1.0\n
0000

Возможности

Класс Capability разбирает протокольные возможности и управляет ими.

PHP
use Pitmaster\Protocol\Capability;

$caps = Capability::parse('thin-pack side-band-64k ofs-delta symref=HEAD:refs/heads/main');

$caps->has('thin-pack');           // true
$caps->get('symref');              // 'HEAD:refs/heads/main'
$caps->all();                      // ['thin-pack' => null, ..., 'symref' => 'HEAD:refs/heads/main']

Распространённые возможности

Возможность Значение
multi_ack Сервер поддерживает согласование multi-ack
thin-pack Сервер отправляет тонкие pack-файлы (базовые объекты могут отсутствовать). Pitmaster запрашивает их, но пока не может индексировать тонкие pack-файлы (#16)
side-band Сервер использует side-band для данных pack (ограничение — 1000 байт)
side-band-64k Сервер использует side-band 64K для данных pack
ofs-delta В pack могут использоваться объекты OFS_DELTA
shallow Сервер поддерживает неглубокое клонирование
no-done Серверу не требуется строка «done»
symref=HEAD:refs/heads/main Символическая цель HEAD
agent=git/2.45.0 Версия сервера git

Демультиплексирование side-band

Когда сервер использует side-band, данные pack передаются вместе с сообщениями о ходе выполнения и ошибках. Первый байт полезной нагрузки каждой строки pkt-line указывает канал:

Канал Значение
1 Данные pack
2 Сообщения о ходе выполнения (stderr)
3 Сообщения о критических ошибках

Pitmaster, UploadPackClient удаляет байты каналов и собирает данные pack обратно.

Версии протокола

v1 (по умолчанию)

Традиционный протокол. При обнаружении ссылок сервер отправляет их все сразу. Pitmaster выполняет согласование за один раунд: отправляет свои want, все локальные вершины ссылок как have, а также done.

v2

Более простой протокол с лучшей производительностью.

Основные отличия от v1:

  • Обнаружение ссылок — отдельная команда ls-refs (можно фильтровать ссылки)
  • Fetch выполняется одной командой fetch со структурированными параметрами
  • Для разделения секций используются пакеты-разделители (0001)

clone() и fetch() всегда используют v1. v2 доступен при явном вызове:

PHP
$discovery = $http->discoverRefsV2($url);       // ls-refs
$packData = (new UploadPackClient($http))->fetchV2($url, $wants, $haves);

Поток запроса HTTP

Клонирование и загрузка

Код
1. GET  /info/refs?service=git-upload-pack
   Response: pkt-line encoded ref list

2. POST /git-upload-pack
   Request:  want <hash>\n ... have <hash>\n ... done\n
   Response: NAK\n + pack data (possibly side-band wrapped)

Отправка

Код
1. GET  /info/refs?service=git-receive-pack
   Response: pkt-line encoded ref list

2. POST /git-receive-pack
   Request:  <old-hash> <new-hash> refs/heads/main\0<capabilities>\n
             0000
             PACK<pack data>
   Response: unpack ok\n + ref status lines

Обработка ошибок

Сетевые ошибки приводят к исключению ProtocolException. Если обмен по smart HTTP для clone() или fetch() завершается сбоем, Pitmaster повторяет операцию через dumb HTTP и при повторной ошибке выбрасывает исходное исключение. Другие сбои (неизвестный удалённый репозиторий, дескриптор без процессов, pack, который невозможно индексировать) приводят к исключениям других типов:

PHP
use Pitmaster\Exceptions\ProtocolException;

try {
    $repo->fetch();
} catch (ProtocolException $e) {
    echo "Network error: {$e->getMessage()}\n";
}

Распространённые ошибки:

  • Сервер недоступен (тайм-аут)
  • Требуется аутентификация или репозиторий не найден (сообщается как Unexpected HTTP status 401, 403 или 404)
  • Неожиданный Content-Type в ответе
  • Некорректные данные pkt-line
  • Сообщение об ошибке в канале side-band