Skip to content

Smart HTTP

Pitmaster communicates with git servers over HTTPS using the smart HTTP protocol. This is the primary network transport. It uses PHP's native stream functions (fopen with a stream context) for HTTP requests, so allow_url_fopen must be enabled.

SmartHttpClient

PHP
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient();
// Or with custom timeout:
$http = new SmartHttpClient(timeout: 60);

The timeout defaults to the PITMASTER_HTTP_TIMEOUT constant (an integer), or 30 seconds when it is not defined.

Discover refs

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() does the same for git-receive-pack. The ref discovery request goes to:

Code
GET <url>/info/refs?service=git-upload-pack

The response is a pkt-line encoded list of refs with capabilities on the first line.

Upload pack (fetch)

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

Sends a POST to <url>/git-upload-pack with the want/have negotiation body. Returns the raw response body (pkt-lines, NAK, and side-band data). UploadPackClient builds the request and extracts the 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);

Sends a POST to <url>/git-receive-pack with the ref update commands and pack data.

PktLine encoding

The git protocol uses pkt-line framing for all communication. Each line is prefixed with its total length (including the 4-byte length prefix) encoded as 4 hex digits.

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)

Length rules

  • Minimum data line: 0005 (1 byte payload)
  • Maximum data line: fff0 (65516 bytes payload)
  • Flush packet: 0000
  • Delimiter packet: 0001
  • Response-end: 0002 (protocol v2; handled by ProtocolV2::decode(), while PktLine::decode() rejects it)
PHP
PktLine::MAX_PAYLOAD;  // 65516 (65520 - 4)

Ref discovery

The RefDiscovery class parses the server's ref advertisement.

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 advertisement format

The first line of the advertisement includes capabilities:

Code
<hash> HEAD\0<capability list>\n
<hash> refs/heads/main\n
<hash> refs/tags/v1.0\n
0000

Capabilities

The Capability class parses and manages protocol capabilities.

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']

Common capabilities

Capability Meaning
multi_ack Server supports multi-ack negotiation
thin-pack Server sends thin packs (base objects may be omitted). Pitmaster requests it but cannot index thin packs yet (#16)
side-band Server uses side-band for pack data (limited to 1000 bytes)
side-band-64k Server uses 64K side-band for pack data
ofs-delta Pack may use OFS_DELTA objects
shallow Server supports shallow clone
no-done Server does not require a "done" line
symref=HEAD:refs/heads/main HEAD's symbolic target
agent=git/2.45.0 Server git version

Side-band demultiplexing

When the server uses side-band, the pack data is multiplexed with progress and error messages. The first byte of each pkt-line payload indicates the channel:

Channel Meaning
1 Pack data
2 Progress messages (stderr)
3 Fatal error messages

Pitmaster's UploadPackClient strips the channel bytes and reassembles the pack data.

Protocol versions

v1 (default)

The traditional protocol. Ref discovery sends all refs at once. Pitmaster negotiates in a single round: it sends its wants, every local ref tip as a have, and done.

v2

A simpler protocol with better performance.

Key differences from v1:

  • Ref discovery is a separate ls-refs command (can filter refs)
  • Fetch is a single fetch command with structured parameters
  • Uses delimiter packets (0001) to separate sections

clone() and fetch() always use v1. v2 is available when you call it explicitly:

PHP
$discovery = $http->discoverRefsV2($url);       // ls-refs
$packData = (new UploadPackClient($http))->fetchV2($url, $wants, $haves);

HTTP request flow

Clone/Fetch

Code
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)

Push

Code
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

Error handling

Network errors throw ProtocolException. When the smart HTTP exchange of clone() or fetch() fails, Pitmaster retries over dumb HTTP and rethrows the original error if that fails too. Other failures (an unknown remote, a process-free handle, a pack that cannot be indexed) throw other exception types:

PHP
use Pitmaster\Exceptions\ProtocolException;

try {
    $repo->fetch();
} catch (ProtocolException $e) {
    echo "Network error: {$e->getMessage()}\n";
}

Common errors:

  • Server unreachable (timeout)
  • Authentication required or repository not found (reported as Unexpected HTTP status 401, 403, or 404)
  • Unexpected Content-Type in the response
  • Invalid pkt-line data
  • Side-band error channel message