Vai al contenuto

Git LFS

Git Large File Storage (LFS) sostituisce i file di grandi dimensioni nel repository con piccoli file puntatore. Il contenuto effettivo viene archiviato su un server LFS separato. Pitmaster può analizzare e creare puntatori LFS e comunicare con i server LFS tramite l’API batch. Non legge .gitattributes né esegue i filtri clean e smudge di LFS: add() archivia i file così come sono e checkout() scrive i file puntatore. Il recupero del contenuto effettivo spetta al vostro codice, come mostrato sotto.

File puntatore LFS

Quando un file è tracciato da LFS, git archivia un file puntatore invece del contenuto effettivo:

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

Il puntatore contiene:

  • version: la versione della specifica LFS URL
  • oid: hash SHA-256 del contenuto effettivo del file (preceduto da sha256:)
  • size: la dimensione effettiva del file in byte

Analizzare i puntatori

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() restituisce null se manca la riga version, oid sha256: o size. Non convalida i valori (accetta un OID non esadecimale o una dimensione non numerica).

Rilevare i puntatori

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

isPointer() verifica la presenza dei campi version URL, oid sha256: e size senza eseguire un’analisi completa.

Creare puntatori

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

Serializzare i puntatori

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

Client LFS

Il LfsClient comunica con i server LFS usando l’API batch (POST a <url>/objects/batch).

Creare un 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);

Il metodo fromRemoteUrl() segue la convenzione standard: aggiunge /info/lfs all’git URL (aggiungendo .git se non è presente). Solo gli URL remoti HTTP(S) forniscono un URL LFS utilizzabile; i remoti SSH in stile SCP (git@host:repo.git) non lo fanno.

LfsClient usa il wrapper di stream HTTP di PHP (allow_url_fopen deve essere abilitato). Non invia credenziali e ignora la mappa header restituita con ogni azione, quindi funziona solo con server LFS e URL di archiviazione che non richiedono autenticazione. Non è associato a un handle del repository, quindi invia richieste di rete anche quando si lavora con un repository aperto con Pitmaster::processFreeOptions().

Scaricare file

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

In pratica, download():

  1. Invia una richiesta batch per l’operazione download
  2. Riceve dal server un URL di download
  3. Recupera il contenuto del file da quell’URL

I byte scaricati non vengono verificati rispetto all’OID o alla dimensione.

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

Caricare file

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

Il caricamento invia una richiesta batch per l’operazione upload. Se il server possiede già l’oggetto, non restituisce alcun URL di caricamento e l’operazione non fa nulla. Il risultato della richiesta di caricamento non viene verificato, quindi un errore di caricamento non viene segnalato e l’azione verify non viene chiamata.

Caricamento 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
}

Protocollo batch API

L’API batch LFS usa JSON su HTTP:

Richiesta

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

Risposta

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

Integrare LFS con le operazioni sul repository

Quando si legge un repository che usa LFS, il contenuto dei blob può essere costituito da puntatori LFS anziché dai dati effettivi dei file. Per ottenere il contenuto effettivo:

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

Gestione degli errori

Una richiesta batch non riuscita o non valida, e una richiesta di download non riuscita, generano ProtocolException. Anche download() genera un errore quando il server non restituisce alcun URL di download per l’oggetto, senza includere il messaggio di errore del server. batchDownload() e batchUpload() non generano errori per gli errori dei singoli oggetti, ma li restituiscono nel campo 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à dei puntatori LFS

La classe LfsPointer è di sola lettura:

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