コンテンツへスキップ

スマートHTTP

PitmasterはスマートHTTPプロトコルを使い、HTTPS経由でgitサーバーと通信します。これが主要なネットワークトランスポートです。HTTPリクエストにはPHPのネイティブストリーム関数(ストリームコンテキスト付きのfopen)を使用するため、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秒です。

refの検出

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に対しても同じ処理を行います。ref検出リクエストの送信先:

コード
GET <url>/info/refs?service=git-upload-pack

応答はpkt-lineでエンコードされたrefの一覧で、先頭行に機能が含まれます。

upload-pack(フェッチ)

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

want/haveネゴシエーションの本文を付けて<url>/git-upload-packにPOSTを送信します。生の応答本文(pkt-line、NAK、サイドバンドデータ)を返します。UploadPackClientはリクエストを構築し、packを抽出します。

PHP
use Pitmaster\Protocol\UploadPackClient;

$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves);   // ObjectId[] wants and haves

receive-pack(プッシュ)

PHP
$response = $http->receivePack($url, $requestBody);

ref更新コマンドとpackデータを付けて<url>/git-receive-packにPOSTを送信します。

PktLineエンコーディング

gitプロトコルでは、すべての通信にpkt-line形式を使用します。各行には、その行の全長(4バイトの長さプレフィックスを含む)を表す4桁の16進数が先頭に付きます。

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バイト)
  • フラッシュパケット:0000
  • 区切りパケット:0001
  • 応答終了:0002(プロトコルv2。ProtocolV2::decode()では処理されますが、PktLine::decode()では拒否されます)
PHP
PktLine::MAX_PAYLOAD;  // 65516 (65520 - 4)

refの検出

RefDiscoveryクラスはサーバーのref通知を解析します。

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

ref通知の形式

通知の先頭行には機能が含まれます。

コード
<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 サーバーがマルチ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は明示的に呼び出すと利用できます:

PHP
$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で再試行し、それも失敗した場合は元のエラーを再スローします。不明なリモート、プロセスを持たないハンドル、インデックス化できないパックなど、その他の失敗では別の例外型がスローされます:

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エラーチャネルのメッセージ