Умен HTTP
Pitmaster комуникира със сървъри на git през HTTPS, използвайки протокола smart HTTP. Това е основният мрежов транспорт. Използва вградените поточни функции на PHP (fopen с контекст на поток) за заявки HTTP, затова 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, като възможностите са на първия ред.
Качване на пакет (извличане)
$response = $http->uploadPack($url, $requestBody);
Изпраща POST заявка до <url>/git-upload-pack с тялото за договаряне want/have. Връща необработеното тяло на отговора (pkt-lines, NAK и данни от страничната лента). UploadPackClient съставя заявката и извлича пакета:
use Pitmaster\Protocol\UploadPackClient;
$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves); // ObjectId[] wants and haves
Получаване на пакет (изпращане)
$response = $http->receivePack($url, $requestBody);
Изпраща POST заявка до <url>/git-receive-pack с командите за актуализиране на препратките и пакетните данни.
Кодиране PktLine
Протоколът git използва рамкиране pkt-line за цялата комуникация. Всеки ред започва с общата си дължина (включително 4-байтовия префикс за дължина), кодирана като 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 |
Сървърът изпраща тънки пакети (базовите обекти може да липсват). 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 е наличен, когато го извикате изрично:
$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 и при повторен неуспех хвърля първоначалната грешка. Други неуспехи (неизвестно отдалечено хранилище, манипулатор без процес, пакет, който не може да бъде индексиран) хвърлят други типове изключения:
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 данни
- Съобщение за грешка от канал на страничната лента