スマートHTTP
PitmasterはスマートHTTPプロトコルを使い、HTTPS経由でgitサーバーと通信します。これが主要なネットワークトランスポートです。HTTPリクエストにはPHPのネイティブストリーム関数(ストリームコンテキスト付きのfopen)を使用するため、allow_url_fopenを有効にする必要があります。
SmartHttpClient
use Pitmaster\Protocol\SmartHttpClient;
$http = new SmartHttpClient();
// Or with custom timeout:
$http = new SmartHttpClient(timeout: 60);
タイムアウトのデフォルトはPITMASTER_HTTP_TIMEOUT定数(整数)です。定義されていない場合は30秒です。
refの検出
$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(フェッチ)
$response = $http->uploadPack($url, $requestBody);
want/haveネゴシエーションの本文を付けて<url>/git-upload-packにPOSTを送信します。生の応答本文(pkt-line、NAK、サイドバンドデータ)を返します。UploadPackClientはリクエストを構築し、packを抽出します。
use Pitmaster\Protocol\UploadPackClient;
$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves); // ObjectId[] wants and haves
receive-pack(プッシュ)
$response = $http->receivePack($url, $requestBody);
ref更新コマンドとpackデータを付けて<url>/git-receive-packにPOSTを送信します。
PktLineエンコーディング
gitプロトコルでは、すべての通信にpkt-line形式を使用します。各行には、その行の全長(4バイトの長さプレフィックスを含む)を表す4桁の16進数が先頭に付きます。
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()では拒否されます)
PktLine::MAX_PAYLOAD; // 65516 (65520 - 4)
refの検出
RefDiscoveryクラスはサーバーのref通知を解析します。
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クラスはプロトコル機能を解析して管理します。
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) |
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は明示的に呼び出すと利用できます:
$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で再試行し、それも失敗した場合は元のエラーを再スローします。不明なリモート、プロセスを持たないハンドル、インデックス化できないパックなど、その他の失敗では別の例外型がスローされます:
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エラーチャネルのメッセージ