Интеллектуальный HTTP
Pitmaster обменивается данными с серверами git через HTTPS по протоколу smart HTTP. Это основной сетевой транспорт. Для запросов HTTP он использует встроенные потоковые функции PHP (fopen с контекстом потока), поэтому необходимо включить allow_url_fopen.
SmartHttpClient
use Pitmaster\Protocol\SmartHttpClient;
$http = new SmartHttpClient();
// Or with custom timeout:
$http = new SmartHttpClient(timeout: 60);
По умолчанию тайм-аут равен константе PITMASTER_HTTP_TIMEOUT (целое число) или 30 секундам, если она не определена.
Обнаружение ссылок
$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)
$response = $http->uploadPack($url, $requestBody);
Отправляет POST-запрос на <url>/git-upload-pack с телом, содержащим согласование want/have. Возвращает исходное тело ответа (строки pkt-line, NAK и данные side-band). UploadPackClient формирует запрос и извлекает pack:
use Pitmaster\Protocol\UploadPackClient;
$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves); // ObjectId[] wants and haves
Получение pack (push)
$response = $http->receivePack($url, $requestBody);
Отправляет POST-запрос на <url>/git-receive-pack с командами обновления ссылок и данными pack.
Кодирование PktLine
Протокол git использует кадрирование pkt-line для всей связи. Перед каждой строкой указывается её общая длина (включая 4-байтовый префикс длины), закодированная четырьмя шестнадцатеричными цифрами.
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()его отклоняет)
PktLine::MAX_PAYLOAD; // 65516 (65520 - 4)
Обнаружение ссылок
Класс RefDiscovery разбирает объявление ссылок сервера.
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 разбирает протокольные возможности и управляет ими.
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 доступен при явном вызове:
$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, который невозможно индексировать) приводят к исключениям других типов:
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