
# 协议

Pitmaster 支持多种网络传输协议，用于与 git 服务器通信。Smart HTTP 是主要传输方式，也提供其他协议以提高兼容性。

## Smart HTTP（主要协议）

推荐使用的传输方式。通过 HTTPS 工作，也是部署最广泛的协议。没有用于自定义标头或凭据的 API；凭据只能通过 URL（`https://user:token@host/repo.git`）传递，PHP 会将其作为 Basic 身份验证发送。`clone()` 会将包含凭据的 URL 作为 `remote.origin.url` 存储在 `.git/config` 中。请求使用 PHP 的流包装器，因此必须启用 `allow_url_fopen`。

```php
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient(timeout: 30);
$discovery = $http->discoverRefs('https://github.com/user/repo.git');
```

详情请参阅[Smart HTTP](https://pitmaster.inline0.dev/zh/docs/network/smart-http)。

### 协议 v1

传统的 smart HTTP 协议。发现阶段会发送所有 refs，协商时使用 want/have/done 行。

```php
use Pitmaster\Protocol\ProtocolV1;
```

### 协议 v2

较新的协议采用结构化命令（`ls-refs`、`fetch`）。对于 refs 众多的仓库，该协议效率更高，因为客户端可以筛选 refs 列表。

`clone()` 和 `fetch()` 始终使用协议 v1。协议 v2 可通过 `SmartHttpClient::discoverRefsV2()` 和 `UploadPackClient::fetchV2()` 经由 smart HTTP 显式使用。

## 哑HTTP

用于不支持 smart HTTP 协议的服务器的回退方案。Dumb 协议通过普通 HTTP GET 请求逐个获取对象和 pack。

```php
use Pitmaster\Protocol\DumbHttpClient;

$client = new DumbHttpClient();
```

Dumb HTTP 的限制：
- 不进行协商（客户端必须下载所有内容）
- 传输过程中不进行 delta 压缩
- 比 smart HTTP 慢得多
- 某些服务器（GitHub、GitLab）不支持

Dumb 协议会获取：
1. `GET /info/refs`（纯文本，不使用 pkt-line）
2. `GET /HEAD`（默认分支）
3. `GET /objects/info/packs`（可用 pack 列表）
4. `GET /objects/pack/pack-<hash>.pack` 和 `.idx`（每个列出的 pack 及其索引）
5. `GET /objects/<xx>/<rest>`（从 ref 顶端遍历获取单个松散对象）

仅当服务器未列出 pack 时才会执行第 5 步。若服务器列出了 pack，则不会下载仅以松散文件形式存在于服务器上的对象（[#18](https://github.com/inline0/pitmaster/issues/18)）。Dumb 客户端仅接受 40 个字符的（SHA-1）对象 ID。smart HTTP 交换失败时，`clone()` 和 `fetch()` 会回退到 dumb HTTP。

## SSH

SSH 传输方式在已加载时使用 `ssh2` PHP 扩展。否则会通过 `proc_open()` 启动系统 `ssh` 客户端。无论哪种情况，都会在远程端运行 `git-upload-pack` 或 `git-receive-pack`。

```php
use Pitmaster\Protocol\SshClient;

$client = new SshClient();
```

SSH URL：
```
git@github.com:user/repo.git
ssh://git@github.com/user/repo.git
```

无需扩展。两种路径的行为有所不同：

**系统 `ssh` 客户端**（`ssh2` 未加载时使用）。Pitmaster 会运行 `ssh -o BatchMode=yes -o ConnectTimeout=30 -p <port> <user>@<host> <command>`，因此身份验证和主机密钥检查遵循你的 SSH 配置及代理。以下环境变量可调整命令：

| 变量 | 作用 |
|----------|--------|
| `PITMASTER_SSH_COMMAND` | 替代 `ssh` 运行的可执行文件 |
| `PITMASTER_SSH_IDENTITY_FILE` | 作为 `-i <file>` 传递 |
| `PITMASTER_SSH_KNOWN_HOSTS` | 作为 `-o UserKnownHostsFile=<file>` 传递 |
| `PITMASTER_SSH_STRICT_HOST_KEY_CHECKING` | 作为 `-o StrictHostKeyChecking=<value>` 传递 |

此路径会启动进程，因此不能用于通过 `['processes' => false]` 打开的句柄。

**`ssh2` 扩展**（加载后始终优先使用）。Pitmaster 使用 `~/.ssh/id_rsa` 进行身份验证；如果 RSA 密钥存在但被拒绝，则改用 `~/.ssh/id_ed25519`；否则使用 SSH 代理。此路径会忽略 `PITMASTER_SSH_*` 变量，且不会验证服务器的主机密钥（[#22](https://github.com/inline0/pitmaster/issues/22)）。

SSH 传输方式会将 `git-upload-pack` 或 `git-receive-pack` 作为远程命令运行，并通过 SSH 通道通信，使用与 smart HTTP 相同的 pkt-line 协议。

## Git 协议（TCP）

原生 git 协议通过 TCP 端口 9418 使用 PHP 套接字通信。`GitProtocolClient` 实现了 refs 发现和 `upload-pack`（获取），但不支持推送。`clone()` 和 `fetch()` 不会将 `git://` URL 路由到此协议，因此请直接使用客户端。

```php
use Pitmaster\Protocol\GitProtocolClient;

$client = new GitProtocolClient();
```

Git URL：
```
git://github.com/user/repo.git
```

特性：
- 无身份验证（实际只能读取）
- 不加密
- 大多数托管服务提供商已弃用
- 使用与 smart HTTP 相同的 pkt-line 分帧方式

git 协议会在初始请求中发送服务名称和仓库路径，随后使用标准 pkt-line 进行 refs 发现和 pack 协商。

## Bundle

Git bundle 是包含 ref 信息的离线 pack 文件，可在没有网络连接时传输仓库数据。

```php
use Pitmaster\Protocol\Bundle;

// Read a bundle file
$bundle = Bundle::open('/path/to/repo.bundle');
$refs = $bundle->refs();
$packData = $bundle->packData();
$prerequisites = $bundle->prerequisites();

// Create one
$bundle = Bundle::create(['refs/heads/main' => $commitId], $packData);
$bundle->writeTo('/path/to/out.bundle');
```

Pitmaster 可读取 v2 和 v3 bundle 标头。没有 `Repository` 方法可导入 bundle：将 `packData()` 写入 `.git/objects/pack/`，使用 `PackIndexer::writeIndex()` 建立索引，然后自行更新 refs。

Bundle 格式：
```
# v2 git bundle\n
-<prerequisite hash>\n    (zero or more)
<hash> <refname>\n        (one or more)
\n
<pack data>               (standard pack file format)
```

Bundle 适用于：
- 通过 USB 驱动器等方式离线传输
- 增量备份
- 在气隙隔离环境中为克隆预置数据

## 浅克隆

Pitmaster 支持浅克隆，只获取有限深度的提交历史。

```php
use Pitmaster\Protocol\ShallowClone;
```

向 `Pitmaster::clone($url, $path, $depth)` 或 `$repo->fetch($remote, $depth)` 传入深度。请求会添加 `shallow` 能力和一行 `deepen`：
```
want <hash>
deepen <depth>
```

服务器会返回 `shallow <hash>` 行，指出历史记录被截断的位置。Pitmaster 根据深度本身计算浅克隆边界；如果计算结果为空，则使用服务器的 `shallow` 行；结果存储在 `.git/shallow` 中。`ShallowClone::readShallow()`、`writeShallow()` 和 `isShallow()` 会直接处理该文件。

## 协议比较

| 协议 | 身份验证 | 加密 | 推送 | 典型用途 |
|----------|------|-----------|------|-------------|
| Smart HTTP | 凭据位于 URL 中 | HTTPS | 是 | 主要传输方式 |
| Dumb HTTP | 凭据位于 URL 中 | HTTPS | 否 | 旧版回退方式 |
| SSH | SSH 密钥或代理 | SSH | 是 | 私有仓库、CI |
| Git (TCP) | 无 | 无 | 否 | 只读镜像 |
| Bundle | 不适用 | 不适用 | 不适用 | 离线传输 |

## 传输方式选择

`Pitmaster::clone()`、`$repo->fetch()` 和 `$repo->push()` 根据 URL 选择传输方式：

| URL 模式 | 传输方式 |
|------------|-----------|
| `ssh://...` | SSH |
| `user@host:path` | SSH |
| 其他情况（`https://...`、`http://...`） | Smart HTTP；clone 和 fetch 会回退到 dumb HTTP |

`git://` URL 和本地路径不受这些方法支持：它们会被发送给 smart HTTP 客户端并失败。对于 `git://`，请直接使用 `GitProtocolClient`。目前，包含用户名和端口的 HTTPS URL（`https://user@host:8443/repo.git`）会被误认为是 SSH URL（[问题 #17](https://github.com/inline0/pitmaster/issues/17)）。
