
# Protocols

Pitmaster supports multiple network transport protocols for communicating with git servers. Smart HTTP is the primary transport. Additional protocols are available for broader compatibility.

## Smart HTTP (primary)

The recommended transport. Works over HTTPS and is the most widely deployed protocol. There is no API for custom headers or credentials; credentials can only be passed in the URL (`https://user:token@host/repo.git`), which PHP sends as Basic authentication. `clone()` stores the URL, credentials included, as `remote.origin.url` in `.git/config`. Requests use PHP's stream wrappers, so `allow_url_fopen` must be enabled.

```php
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient(timeout: 30);
$discovery = $http->discoverRefs('https://github.com/user/repo.git');
```

See [Smart HTTP](/docs/network/smart-http) for full details.

### Protocol v1

The traditional smart HTTP protocol. All refs are sent during discovery, and negotiation uses want/have/done lines.

```php
use Pitmaster\Protocol\ProtocolV1;
```

### Protocol v2

The newer protocol with structured commands (`ls-refs`, `fetch`). More efficient for repositories with many refs because the client can filter the ref list.

`clone()` and `fetch()` always use protocol v1. Protocol v2 is available explicitly over smart HTTP through `SmartHttpClient::discoverRefsV2()` and `UploadPackClient::fetchV2()`.

## Dumb HTTP

Fallback for servers that do not support the smart HTTP protocol. The dumb protocol fetches objects and packs individually via plain HTTP GET requests.

```php
use Pitmaster\Protocol\DumbHttpClient;

$client = new DumbHttpClient();
```

Dumb HTTP limitations:
- No negotiation (client must download everything)
- No delta compression during transfer
- Significantly slower than smart HTTP
- Some servers (GitHub, GitLab) do not support it

The dumb protocol fetches:
1. `GET /info/refs` (plain text, no pkt-line)
2. `GET /HEAD` (default branch)
3. `GET /objects/info/packs` (list of available packs)
4. `GET /objects/pack/pack-<hash>.pack` and `.idx` (each listed pack and its index)
5. `GET /objects/<xx>/<rest>` (individual loose objects, walked from the ref tips)

Step 5 runs only when the server lists no packs. When it lists packs, objects that exist only as loose files on the server are not downloaded ([#18](https://github.com/inline0/pitmaster/issues/18)). The dumb client accepts 40-character (SHA-1) object IDs only. `clone()` and `fetch()` fall back to dumb HTTP when the smart HTTP exchange fails.

## SSH

SSH transport uses the `ssh2` PHP extension when it is loaded. Otherwise it starts the system `ssh` client through `proc_open()`. Either way it runs `git-upload-pack` or `git-receive-pack` on the remote.

```php
use Pitmaster\Protocol\SshClient;

$client = new SshClient();
```

SSH URLs:
```
git@github.com:user/repo.git
ssh://git@github.com/user/repo.git
```

No extension is required. The two paths behave differently:

**System `ssh` client** (used when `ssh2` is not loaded). Pitmaster runs `ssh -o BatchMode=yes -o ConnectTimeout=30 -p <port> <user>@<host> <command>`, so authentication and host key checking follow your SSH configuration and agent. These environment variables adjust the command:

| Variable | Effect |
|----------|--------|
| `PITMASTER_SSH_COMMAND` | Executable to run instead of `ssh` |
| `PITMASTER_SSH_IDENTITY_FILE` | Passed as `-i <file>` |
| `PITMASTER_SSH_KNOWN_HOSTS` | Passed as `-o UserKnownHostsFile=<file>` |
| `PITMASTER_SSH_STRICT_HOST_KEY_CHECKING` | Passed as `-o StrictHostKeyChecking=<value>` |

This path starts a process, so it is not available on handles opened with `['processes' => false]`.

**`ssh2` extension** (always preferred when loaded). Pitmaster authenticates with `~/.ssh/id_rsa`, then `~/.ssh/id_ed25519` if the RSA key is present but rejected, and otherwise with the SSH agent. The `PITMASTER_SSH_*` variables are ignored, and the server's host key is not verified on this path ([#22](https://github.com/inline0/pitmaster/issues/22)).

The SSH transport runs `git-upload-pack` or `git-receive-pack` as a remote command and communicates over the SSH channel using the same pkt-line protocol as smart HTTP.

## Git protocol (TCP)

The native git protocol communicates over TCP port 9418 using PHP sockets. `GitProtocolClient` implements ref discovery and `upload-pack` (fetching); it cannot push. `clone()` and `fetch()` do not route `git://` URLs to it, so use the client directly.

```php
use Pitmaster\Protocol\GitProtocolClient;

$client = new GitProtocolClient();
```

Git URLs:
```
git://github.com/user/repo.git
```

Characteristics:
- No authentication (read-only in practice)
- No encryption
- Deprecated by most hosting providers
- Uses the same pkt-line framing as smart HTTP

The git protocol sends the service name and repository path as the initial request, then proceeds with standard pkt-line ref discovery and pack negotiation.

## Bundles

Git bundles are offline pack files with ref information. They allow transferring repository data without a network connection.

```php
use Pitmaster\Protocol\Bundle;

// Read a bundle file
$bundle = Bundle::open('/path/to/repo.bundle');
$refs = $bundle->refs();
$packData = $bundle->packData();
$prerequisites = $bundle->prerequisites();

// Create one
$bundle = Bundle::create(['refs/heads/main' => $commitId], $packData);
$bundle->writeTo('/path/to/out.bundle');
```

Pitmaster reads v2 and v3 bundle headers. There is no `Repository` method to import a bundle: write `packData()` to `.git/objects/pack/`, build its index with `PackIndexer::writeIndex()`, and update the refs yourself.

Bundle format:
```
# v2 git bundle\n
-<prerequisite hash>\n    (zero or more)
<hash> <refname>\n        (one or more)
\n
<pack data>               (standard pack file format)
```

Bundles are useful for:
- Sneakernet transfers (USB drives)
- Incremental backups
- Seeding clones in air-gapped environments

## Shallow clones

Pitmaster supports shallow clones, where only a limited depth of commit history is fetched.

```php
use Pitmaster\Protocol\ShallowClone;
```

Pass a depth to `Pitmaster::clone($url, $path, $depth)` or `$repo->fetch($remote, $depth)`. The request adds the `shallow` capability and a `deepen` line:
```
want <hash>
deepen <depth>
```

The server responds with `shallow <hash>` lines indicating where the history was truncated. Pitmaster computes the shallow boundary from the depth itself and uses the server's `shallow` lines when that computation finds nothing; the result is stored in `.git/shallow`. `ShallowClone::readShallow()`, `writeShallow()`, and `isShallow()` work with that file directly.

## Protocol comparison

| Protocol | Auth | Encryption | Push | Typical use |
|----------|------|-----------|------|-------------|
| Smart HTTP | Credentials in the URL | HTTPS | Yes | Primary transport |
| Dumb HTTP | Credentials in the URL | HTTPS | No | Legacy fallback |
| SSH | SSH keys or agent | SSH | Yes | Private repos, CI |
| Git (TCP) | None | None | No | Read-only mirrors |
| Bundle | N/A | N/A | N/A | Offline transfer |

## Transport selection

`Pitmaster::clone()`, `$repo->fetch()`, and `$repo->push()` select the transport from the URL:

| URL pattern | Transport |
|------------|-----------|
| `ssh://...` | SSH |
| `user@host:path` | SSH |
| Anything else (`https://...`, `http://...`) | Smart HTTP; clone and fetch fall back to dumb HTTP |

`git://` URLs and local paths are not supported by these methods: they are sent to the smart HTTP client and fail. Use `GitProtocolClient` directly for `git://`. An HTTPS URL that contains both a user and a port (`https://user@host:8443/repo.git`) is currently mistaken for an SSH URL ([#17](https://github.com/inline0/pitmaster/issues/17)).
