
# スマートHTTP

PitmasterはスマートHTTPプロトコルを使い、HTTPS経由でgitサーバーと通信します。これが主要なネットワークトランスポートです。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秒です。

### refの検出

```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`に対しても同じ処理を行います。ref検出リクエストの送信先：
```
GET <url>/info/refs?service=git-upload-pack
```

応答はpkt-lineでエンコードされたrefの一覧で、先頭行に機能が含まれます。

### upload-pack（フェッチ）

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

want/haveネゴシエーションの本文を付けて`<url>/git-upload-pack`にPOSTを送信します。生の応答本文（pkt-line、`NAK`、サイドバンドデータ）を返します。`UploadPackClient`はリクエストを構築し、packを抽出します。

```php
use Pitmaster\Protocol\UploadPackClient;

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

### receive-pack（プッシュ）

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

ref更新コマンドとpackデータを付けて`<url>/git-receive-pack`にPOSTを送信します。

## PktLineエンコーディング

gitプロトコルでは、すべての通信にpkt-line形式を使用します。各行には、その行の全長（4バイトの長さプレフィックスを含む）を表す4桁の16進数が先頭に付きます。

```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)
```

## refの検出

`RefDiscovery`クラスはサーバーのref通知を解析します。

```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
```

### ref通知の形式

通知の先頭行には機能が含まれます。

```
<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` | サーバーがマルチACKネゴシエーションをサポート |
| `thin-pack` | サーバーが薄いpackを送信（ベースオブジェクトが省略される場合あり）。Pitmasterはこれを要求しますが、現時点では薄いpackのインデックスを作成できません（[#16](https://github.com/inline0/pitmaster/issues/16)） |
| `side-band` | サーバーがpackデータにサイドバンドを使用（最大1000バイト） |
| `side-band-64k` | サーバーがpackデータに64Kサイドバンドを使用 |
| `ofs-delta` | packでOFS_DELTAオブジェクトを使用する場合あり |
| `shallow` | サーバーがシャロークローンをサポート |
| `no-done` | サーバーが「done」行を要求しない |
| `symref=HEAD:refs/heads/main` | HEADのシンボリックターゲット |
| `agent=git/2.45.0` | サーバーのgitバージョン |

## サイドバンドの多重化解除

サーバーがサイドバンドを使用する場合、packデータは進捗メッセージやエラーメッセージと多重化されます。各pkt-lineペイロードの先頭バイトがチャネルを示します。

| チャネル | 意味 |
|---------|---------|
| 1 | packデータ |
| 2 | 進捗メッセージ（stderr） |
| 3 | 致命的なエラーメッセージ |

Pitmasterの`UploadPackClient`はチャネルバイトを取り除き、packデータを再構成します。

## プロトコルのバージョン

### v1（デフォルト）

従来のプロトコルです。refの検出ではすべてのrefを一度に送信します。Pitmasterは1回のやり取りでネゴシエーションし、要求するref、`have`としてのすべてのローカルrefの先端、`done`を送信します。

### v2

より高いパフォーマンスを実現する、よりシンプルなプロトコルです。

v1との主な違い:
- Refの検出は独立した`ls-refs`コマンドで行います（Refを絞り込めます）
- 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`をスローします。スマートHTTPで`clone()`または`fetch()`の交換に失敗すると、Pitmasterはダム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エラーチャネルのメッセージ
