
# Интеллектуальный 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](https://github.com/inline0/pitmaster/issues/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
