스마트 HTTP
Pitmaster은 smart 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 (fetch)
$response = $http->uploadPack($url, $requestBody);
want/have 협상 본문과 함께 <url>/git-upload-pack으로 POST를 보냅니다. 원시 응답 본문(pkt-line, NAK 및 side-band 데이터)을 반환합니다. UploadPackClient가 요청을 구성하고 pack을 추출합니다:
use Pitmaster\Protocol\UploadPackClient;
$client = new UploadPackClient($http);
$packData = $client->fetch($url, $wants, $haves); // ObjectId[] wants and haves
receive pack (push)
$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바이트) - Flush 패킷:
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 |
서버가 multi-ack 협상을 지원함 |
thin-pack |
서버가 thin pack을 전송함(기본 객체가 생략될 수 있음). Pitmaster가 이를 요청하지만 아직 thin pack을 인덱싱할 수 없음(#16) |
side-band |
서버가 pack 데이터에 side-band를 사용함(1000바이트로 제한) |
side-band-64k |
서버가 pack 데이터에 64K side-band를 사용함 |
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 (기본값)
기존 프로토콜입니다. ref 검색 시 모든 ref를 한 번에 전송합니다. Pitmaster은 단일 라운드로 협상합니다. 원하는 항목, 모든 로컬 ref tip을 have으로, 그리고 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을 발생시킵니다. 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 오류 채널 메시지