
# 智能 HTTP

Pitmaster 使用 smart HTTP 协议，通过 HTTPS 与 git 服务器通信。这是主要的网络传输方式。它使用 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 编码的引用列表，第一行包含功能协商信息。

### 上传 pack（fetch）

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

向 `<url>/git-upload-pack` 发送 POST 请求，并附上 want/have 协商正文。返回原始响应正文（pkt-lines、`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);
```

向 `<url>/git-receive-pack` 发送 POST 请求，并附上引用更新命令和 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 字节负载）
- Flush 包：`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` | 服务器发送 thin pack（可能省略基础对象）。Pitmaster 会请求此功能，但目前无法索引 thin pack（[问题 #16](https://github.com/inline0/pitmaster/issues/16)） |
| `side-band` | 服务器使用 side-band 传输 pack 数据（上限为 1000 字节） |
| `side-band-64k` | 服务器使用 64K side-band 传输 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 通过单轮协商：发送 wants、每个本地引用顶端作为 `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 请求流程

### 克隆/Fetch

```
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 错误通道消息
