
# Умен 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](https://github.com/inline0/pitmaster/issues/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 данни
- Съобщение за грешка от канал на страничната лента
