본문으로 건너뛰기

스마트 HTTP

Pitmaster은 smart 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 (fetch)

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

want/have 협상 본문과 함께 <url>/git-upload-pack으로 POST를 보냅니다. 원시 응답 본문(pkt-line, NAK 및 side-band 데이터)을 반환합니다. UploadPackClient가 요청을 구성하고 pack을 추출합니다:

PHP
use Pitmaster\Protocol\UploadPackClient;

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

receive pack (push)

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바이트)
  • Flush 패킷: 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 서버가 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은 명시적으로 호출할 때 사용할 수 있습니다:

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을 발생시킵니다. 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 오류 채널 메시지