智能 HTTP
Pitmaster 使用 smart HTTP 协议,通过 HTTPS 与 git 服务器通信。这是主要的网络传输方式。它使用 PHP 的原生流函数(fopen 配合流上下文)发送 HTTP 请求,因此必须启用 allow_url_fopen。
SmartHttpClient
use Pitmaster\Protocol\SmartHttpClient;
$http = new SmartHttpClient();
// Or with custom timeout:
$http = new SmartHttpClient(timeout: 60);
超时默认为 PITMASTER_HTTP_TIMEOUT 常量(整数);如果未定义,则为 30 秒。
发现引用
$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)
$response = $http->uploadPack($url, $requestBody);
向 <url>/git-upload-pack 发送 POST 请求,并附上 want/have 协商正文。返回原始响应正文(pkt-lines、NAK 和 side-band 数据)。UploadPackClient 会构建请求并提取 pack:
use Pitmaster\Protocol\UploadPackClient;
$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves); // ObjectId[] wants and haves
接收 pack(push)
$response = $http->receivePack($url, $requestBody);
向 <url>/git-receive-pack 发送 POST 请求,并附上引用更新命令和 pack 数据。
PktLine 编码
git 协议使用 pkt-line 帧格式进行所有通信。每行前面都有一个表示总长度的前缀(包括 4 字节长度前缀),以 4 位十六进制数字编码。
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()会拒绝它)
PktLine::MAX_PAYLOAD; // 65516 (65520 - 4)
引用发现
RefDiscovery 类会解析服务器的引用通告。
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 类会解析并管理协议功能。
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) |
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 时可以使用它:
$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)会抛出其他异常类型:
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 错误通道消息