Към съдържанието

Умен 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, като възможностите са на първия ред.

Качване на пакет (извличане)

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

Изпраща POST заявка до <url>/git-upload-pack с тялото за договаряне want/have. Връща необработеното тяло на отговора (pkt-lines, NAK и данни от страничната лента). UploadPackClient съставя заявката и извлича пакета:

PHP
use Pitmaster\Protocol\UploadPackClient;

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

Получаване на пакет (изпращане)

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 Сървърът използва странична лента за пакетните данни (ограничени до 1000 байта)
side-band-64k Сървърът използва странична лента 64K за пакетните данни
ofs-delta Пакетът може да използва обекти OFS_DELTA
shallow Сървърът поддържа плитки клонинги
no-done Сървърът не изисква ред "done"
symref=HEAD:refs/heads/main Символичната цел на HEAD
agent=git/2.45.0 Версията на git на сървъра

Демултиплексиране на страничната лента

Когато сървърът използва странична лента, пакетните данни се мултиплексират със съобщенията за напредък и грешки. Първият байт от полезния товар на всеки pkt-line указва канала:

Канал Значение
1 Пакетни данни
2 Съобщения за напредъка (stderr)
3 Съобщения за фатални грешки

Pitmaster на UploadPackClient премахва байтовете на каналите и сглобява отново пакетните данни.

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

v1 (по подразбиране)

Традиционният протокол. При откриването се изпращат всички препратки наведнъж. Pitmaster договаря в един кръг: изпраща заявените обекти, върха на всяка локална препратка като have и done.

v2

По-опростен протокол с по-добра производителност.

Основни разлики спрямо v1:

  • Откриването на препратки е отделна команда ls-refs (може да филтрира препратките)
  • Извличането е една команда 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 данни
  • Съобщение за грешка от канал на страничната лента