Treci la conținut

Git LFS

Git Large File Storage (LFS) înlocuiește fișierele mari din depozit cu fișiere pointer mici. Conținutul real al fișierelor este stocat pe un server LFS separat. Pitmaster poate analiza și crea pointere LFS și poate comunica cu serverele LFS prin API batch. Nu citește .gitattributes și nu rulează filtrele LFS clean și smudge: add() stochează fișierele așa cum sunt, iar checkout() scrie fișiere pointer. Preluarea conținutului real revine codului dumneavoastră, după cum se arată mai jos.

Fișiere pointer LFS

Când un fișier este urmărit de LFS, git stochează un fișier pointer în locul conținutului real:

Cod
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345

Pointerul conține:

  • version: versiunea specificației LFS URL
  • oid: hash-ul SHA-256 al conținutului real al fișierului (cu prefixul sha256:)
  • size: dimensiunea reală a fișierului în octeți

Analizarea pointerelor

PHP
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() returnează null dacă lipsește linia version, oid sha256: sau size. Nu validează valorile (un OID non-hexazecimal sau o dimensiune nenumerică este acceptată).

Detectarea pointerelor

PHP
if (LfsPointer::isPointer($blobContent)) {
    // This blob is an LFS pointer, not actual file content
    $pointer = LfsPointer::parse($blobContent);
}

isPointer() verifică prezența câmpurilor versiune URL, oid sha256: și size fără analizare completă.

Crearea pointerelor

PHP
// 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

Serializarea pointerelor

PHP
$content = $pointer->serialize();
// "version https://git-lfs.github.com/spec/v1\noid sha256:4d7a21...\nsize 12345\n"

Client LFS

LfsClient comunică cu serverele LFS folosind API batch (POST către <url>/objects/batch).

Crearea unui client

PHP
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);

Metoda fromRemoteUrl() urmează convenția standard: adaugă /info/lfs la git URL (adăugând .git dacă lipsește). Numai URL-urile remote HTTP(S) oferă un URL LFS utilizabil; remote-urile în stil SCP SSH (git@host:repo.git) nu oferă unul.

LfsClient folosește wrapper-ul de flux HTTP din PHP (trebuie activat allow_url_fopen). Nu trimite credențiale și ignoră maparea header returnată pentru fiecare acțiune, deci funcționează doar cu servere LFS și URL-uri de stocare care nu necesită autentificare. Nu este legat de un handle de depozit, așa că efectuează cereri de rețea chiar și când lucrați cu un depozit deschis cu Pitmaster::processFreeOptions().

Descărcarea fișierelor

PHP
// Single file download
$content = $client->download($pointer->oid, $pointer->size);
file_put_contents('large-file.bin', $content);

În fundal, download():

  1. Trimite o cerere batch pentru operațiunea download
  2. Primește de la server un URL de descărcare
  3. Preia conținutul fișierului de la acel URL

Octeții descărcați nu sunt verificați în raport cu OID-ul sau dimensiunea.

Descărcare batch

PHP
$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']
    }
}

Încărcarea fișierelor

PHP
$client->upload($pointer->oid, $pointer->size, $fileContent);

Încărcarea trimite o cerere batch pentru operațiunea upload. Dacă serverul are deja obiectul, nu returnează niciun URL de încărcare, iar operațiunea nu face nimic. Rezultatul cererii de încărcare în sine nu este verificat, așa că o încărcare eșuată nu este raportată, iar acțiunea verify nu este apelată.

Încărcare batch

PHP
$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
}

Protocolul API batch

Batch-ul LFS API folosește JSON prin HTTP:

Cerere

Cod
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}
    ]
}

Răspuns

JSON
{
    "objects": [
        {
            "oid": "abc123...",
            "size": 12345,
            "actions": {
                "download": {
                    "href": "https://storage.example.com/abc123...",
                    "header": {
                        "Authorization": "Bearer token"
                    }
                }
            }
        }
    ]
}

Integrarea LFS cu operațiunile asupra depozitului

Când citiți un depozit care folosește LFS, conținutul blob-urilor poate consta în pointere LFS în locul datelor reale ale fișierelor. Pentru a obține conținutul real:

PHP
$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;
}

Tratarea erorilor

O cerere batch eșuată sau nevalidă, precum și o cerere de descărcare eșuată, aruncă ProtocolException. download() aruncă, de asemenea, o excepție când serverul nu returnează niciun URL de descărcare pentru obiect, fără mesajul de eroare al serverului. batchDownload() și batchUpload() nu aruncă excepții pentru erori la nivel de obiect; le returnează în câmpul error.

PHP
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";
}

Proprietățile pointerelor LFS

Clasa LfsPointer este numai pentru citire:

PHP
$pointer->oid;      // string: SHA-256 hash (hex)
$pointer->size;     // int: file size in bytes
$pointer->version;  // string: spec version URL