跳转到正文

智能 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)
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 错误通道消息