Git LFS
Git Large File Storage (LFS) ersetzt große Dateien in deinem Repository durch kleine Zeigerdateien. Der tatsächliche Dateiinhalt wird auf einem separaten LFS-Server gespeichert. Pitmaster kann LFS-Zeiger einlesen und erstellen sowie über das Batch-API mit LFS-Servern kommunizieren. Es liest weder .gitattributes noch führt es die LFS-Clean- und Smudge-Filter aus: add() speichert Dateien unverändert, und checkout() schreibt Zeigerdateien. Deinen Code musst du selbst dafür verwenden, den tatsächlichen Inhalt abzurufen, wie unten gezeigt.
LFS-Zeigerdateien
Wenn eine Datei von LFS verwaltet wird, speichert git statt des tatsächlichen Inhalts eine Zeigerdatei:
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345
Die Zeigerdatei enthält:
version: die LFS-Spezifikationsversion URLoid: den SHA-256-Hash des tatsächlichen Datei-Inhalts (mit dem Präfixsha256:)size: die tatsächliche Dateigröße in Byte
Zeiger einlesen
use Pitmaster\Lfs\LfsPointer;
$content = file_get_contents('.git-lfs-tracked-file');
$pointer = LfsPointer::parse($content);
if ($pointer !== null) {
echo "OID: {$pointer->oid}\n";
echo "Size: {$pointer->size}\n";
echo "Version: {$pointer->version}\n";
}
parse() gibt null zurück, wenn die Zeile version, oid sha256: oder size fehlt. Die Werte werden nicht validiert (eine nicht-hexadezimale OID oder eine nicht numerische Größe wird akzeptiert).
Zeiger erkennen
if (LfsPointer::isPointer($blobContent)) {
// This blob is an LFS pointer, not actual file content
$pointer = LfsPointer::parse($blobContent);
}
isPointer() prüft, ob die Felder Version URL, oid sha256: und size vorhanden sind, ohne den Inhalt vollständig einzulesen.
Zeiger erstellen
// From known OID and size
$pointer = new LfsPointer(
oid: '4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393',
size: 12345,
);
// From file content (computes SHA-256 hash)
$pointer = LfsPointer::forContent($actualFileContent);
echo $pointer->oid; // SHA-256 hash
echo $pointer->size; // Content length
Zeiger serialisieren
$content = $pointer->serialize();
// "version https://git-lfs.github.com/spec/v1\noid sha256:4d7a21...\nsize 12345\n"
LFS-Client
Der LfsClient kommuniziert mit LFS-Servern über das Batch-API (POST an <url>/objects/batch).
Client erstellen
use Pitmaster\Lfs\LfsClient;
// From a git remote URL (appends /info/lfs)
$client = LfsClient::fromRemoteUrl('https://github.com/user/repo.git');
// LFS URL: https://github.com/user/repo.git/info/lfs
// Direct URL
$client = new LfsClient('https://lfs.example.com/repo', timeout: 60);
Die Methode fromRemoteUrl() folgt der üblichen Konvention: /info/lfs an das git-URL anhängen (.git hinzufügen, falls nicht vorhanden). Nur HTTP(S)-Remote-URLs liefern einen nutzbaren LFS-URL; SCP-artige SSH-Remotes (git@host:repo.git) funktionieren nicht.
LfsClient verwendet den Stream-Wrapper HTTP von PHP (allow_url_fopen muss aktiviert sein). Es sendet keine Zugangsdaten und ignoriert die mit jeder Aktion zurückgegebene header-Map. Daher funktioniert es nur mit LFS-Servern und Speicher-URLs, die keine Authentifizierung erfordern. Es ist nicht an ein Repository-Handle gebunden und stellt daher auch dann Netzwerkanfragen, wenn du mit einem Repository arbeitest, das mit Pitmaster::processFreeOptions() geöffnet wurde.
Dateien herunterladen
// Single file download
$content = $client->download($pointer->oid, $pointer->size);
file_put_contents('large-file.bin', $content);
Im Hintergrund führt download() Folgendes aus:
- Sendet eine Batch-Anfrage für die Operation
download - Empfängt vom Server eine Download-URL
- Ruft den Dateiinhalt über diese URL ab
Die heruntergeladenen Bytes werden nicht mit der OID oder der Größe abgeglichen.
Batch-Download
$results = $client->batchDownload([
['oid' => 'abc123...', 'size' => 12345],
['oid' => 'def456...', 'size' => 67890],
]);
foreach ($results as $result) {
if ($result['error'] !== null) {
echo "Error: {$result['error']}\n";
continue;
}
if ($result['href'] !== null) {
// Download from $result['href']
}
}
Dateien hochladen
$client->upload($pointer->oid, $pointer->size, $fileContent);
Beim Hochladen wird eine Batch-Anfrage für die Operation upload gesendet. Hat der Server das Objekt bereits, gibt er keine Upload-URL zurück und der Vorgang ist ein No-op. Das Ergebnis der Upload-Anfrage selbst wird nicht geprüft. Ein fehlgeschlagener Upload wird daher nicht gemeldet und die Aktion verify wird nicht aufgerufen.
Batch-Upload
$results = $client->batchUpload([
['oid' => 'abc123...', 'size' => 12345],
]);
foreach ($results as $result) {
if ($result['href'] !== null) {
// Server needs this object, upload to $result['href']
}
// No href means server already has it
}
Batch-API-Protokoll
Das LFS-Batch-API verwendet JSON über HTTP:
Anfrage
POST <lfs-url>/objects/batch
Content-Type: application/vnd.git-lfs+json
Accept: application/vnd.git-lfs+json
{
"operation": "download",
"transfers": ["basic"],
"objects": [
{"oid": "abc123...", "size": 12345}
]
}
Antwort
{
"objects": [
{
"oid": "abc123...",
"size": 12345,
"actions": {
"download": {
"href": "https://storage.example.com/abc123...",
"header": {
"Authorization": "Bearer token"
}
}
}
}
]
}
LFS in Repository-Operationen integrieren
Beim Lesen eines Repositorys mit LFS können Blob-Inhalte LFS-Zeiger statt der tatsächlichen Dateidaten sein. So rufst du den tatsächlichen Inhalt ab:
$blob = $repo->readObject($hash);
if (LfsPointer::isPointer($blob->content)) {
$pointer = LfsPointer::parse($blob->content);
$remoteUrl = $repo->config()->get('remote.origin.url');
$client = LfsClient::fromRemoteUrl($remoteUrl);
$realContent = $client->download($pointer->oid, $pointer->size);
} else {
$realContent = $blob->content;
}
Fehlerbehandlung
Eine fehlgeschlagene oder ungültige Batch-Anfrage sowie eine fehlgeschlagene Download-Anfrage lösen ProtocolException aus. download() löst ebenfalls einen Fehler aus, wenn der Server für das Objekt keine Download-URL zurückgibt; die Fehlermeldung des Servers wird dabei nicht übernommen. batchDownload() und batchUpload() lösen bei Fehlern einzelner Objekte keinen Fehler aus, sondern geben sie im Feld error zurück.
use Pitmaster\Exceptions\ProtocolException;
try {
$content = $client->download($oid, $size);
} catch (ProtocolException $e) {
// Server unreachable, invalid response, no download URL
echo "LFS error: {$e->getMessage()}\n";
}
Eigenschaften von LFS-Zeigern
Die Klasse LfsPointer ist schreibgeschützt:
$pointer->oid; // string: SHA-256 hash (hex)
$pointer->size; // int: file size in bytes
$pointer->version; // string: spec version URL