Aller au contenu

Git LFS

Git Large File Storage (LFS) remplace les fichiers volumineux de votre dépôt par de petits fichiers pointeurs. Le contenu réel des fichiers est stocké sur un serveur LFS distinct. Pitmaster peut analyser et créer des pointeurs LFS et communiquer avec les serveurs LFS via le API par lots. Il ne lit pas .gitattributes et n’exécute pas les filtres clean et smudge de LFS : add() stocke les fichiers tels quels et checkout() écrit des fichiers pointeurs. Il revient à votre code de récupérer le contenu réel, comme illustré ci-dessous.

Fichiers pointeurs LFS

Lorsqu’un fichier est suivi par LFS, git stocke un fichier pointeur au lieu du contenu réel :

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

Le pointeur contient :

  • version : la version de la spécification LFS URL
  • oid : le hash SHA-256 du contenu réel du fichier (préfixé par sha256:)
  • size : la taille réelle du fichier en octets

Analyser les pointeurs

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() renvoie null si la ligne version, oid sha256: ou size est absente. Il ne valide pas les valeurs (un OID non hexadécimal ou une taille non numérique est accepté).

Détecter les pointeurs

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

isPointer() vérifie la présence des champs version URL, oid sha256: et size sans analyse complète.

Créer des pointeurs

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

Sérialiser les pointeurs

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

Client LFS

Le LfsClient communique avec les serveurs LFS à l’aide du API par lots (POST vers <url>/objects/batch).

Créer 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);

La méthode fromRemoteUrl() suit la convention habituelle : elle ajoute /info/lfs à l’URL git (en ajoutant .git si nécessaire). Seules les URL distantes HTTP(S) fournissent un URL LFS utilisable ; les dépôts distants au format SCP SSH (git@host:repo.git) ne fonctionnent pas.

LfsClient utilise le wrapper de flux HTTP de PHP (allow_url_fopen doit être activé). Il n’envoie aucun identifiant et ignore la map header renvoyée avec chaque action ; il ne fonctionne donc qu’avec les serveurs LFS et les URL de stockage qui ne nécessitent aucune authentification. Il n’est pas lié à un handle de dépôt et effectue donc des requêtes réseau même lorsque vous travaillez avec un dépôt ouvert avec Pitmaster::processFreeOptions().

Télécharger des fichiers

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

En interne, download() :

  1. Envoie une requête par lots pour l’opération download
  2. Reçoit une URL de téléchargement du serveur
  3. Récupère le contenu du fichier depuis cette URL

Les octets téléchargés ne sont pas vérifiés par rapport à l’OID ou à la taille.

Téléchargement par lots

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

Téléverser des fichiers

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

Le téléversement envoie une requête par lots pour l’opération upload. Si le serveur possède déjà l’objet, il ne renvoie aucune URL de téléversement et l’opération ne fait rien. Le résultat de la requête de téléversement n’est pas vérifié ; un échec n’est donc pas signalé et l’action verify n’est pas appelée.

Téléversement par lots

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
}

Protocole API par lots

Le API par lots de LFS utilise JSON sur HTTP :

Requête

Code
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éponse

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

Intégrer LFS aux opérations du dépôt

Lors de la lecture d’un dépôt utilisant LFS, le contenu des blobs peut être constitué de pointeurs LFS plutôt que des données réelles des fichiers. Pour obtenir le contenu réel :

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

Gestion des erreurs

Une requête par lots invalide ou en échec, ainsi qu’une requête de téléchargement en échec, provoquent ProtocolException. download() provoque aussi une erreur lorsque le serveur ne renvoie aucune URL de téléchargement pour l’objet, sans inclure le message d’erreur du serveur. batchDownload() et batchUpload() ne provoquent pas d’erreur pour les erreurs par objet ; elles les renvoient dans le champ 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";
}

Propriétés des pointeurs LFS

La classe LfsPointer est en lecture seule :

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