Перейти до вмісту

Розумний HTTP

Pitmaster обмінюється даними із серверами git через HTTPS, використовуючи протокол smart HTTP. Це основний мережевий транспорт. Він використовує власні потокові функції PHP (fopen із контекстом потоку) для запитів HTTP, тому 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 список посилань із можливостями в першому рядку.

Надсилання пакета (fetch)

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

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

PHP
use Pitmaster\Protocol\UploadPackClient;

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

Отримання пакета (push)

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

Надсилає POST до <url>/git-receive-pack із командами оновлення посилань і даними пакета.

Кодування PktLine

Протокол git використовує кадрування pkt-line для всього обміну даними. Перед кожним рядком указується його загальна довжина (разом із 4-байтовим префіксом довжини), закодована як 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 Сервер надсилає тонкі пакети (базові об’єкти можуть бути відсутні). Pitmaster запитує їх, але поки не може індексувати тонкі пакети (#16)
side-band Сервер використовує side-band для даних пакета (обмеження — 1000 байтів)
side-band-64k Сервер використовує side-band 64K для даних пакета
ofs-delta У пакеті можуть бути об’єкти OFS_DELTA
shallow Сервер підтримує поверхневе клонування
no-done Серверу не потрібен рядок "done"
symref=HEAD:refs/heads/main Символьна ціль HEAD
agent=git/2.45.0 Версія git сервера

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

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

Канал Значення
1 Дані пакета
2 Повідомлення про перебіг (stderr)
3 Повідомлення про критичні помилки

Pitmaster видаляє байти каналів із UploadPackClient і повторно збирає дані пакета.

Версії протоколу

v1 (за замовчуванням)

Традиційний протокол. Під час виявлення посилань надсилаються всі посилання одразу. Pitmaster узгоджує дані за один цикл: надсилає потрібні йому об’єкти, кожну локальну вершину посилання як 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 і, якщо вона теж не вдається, повторно викидає початкову помилку. Інші збої (невідомий віддалений репозиторій, дескриптор без процесу, пакет, який неможливо індексувати) спричиняють винятки інших типів:

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