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 :
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345
Le pointeur contient :
version: la version de la spécification LFS URLoid: le hash SHA-256 du contenu réel du fichier (préfixé parsha256:)size: la taille réelle du fichier en octets
Analyser les pointeurs
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
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
// 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
$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
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
// Single file download
$content = $client->download($pointer->oid, $pointer->size);
file_put_contents('large-file.bin', $content);
En interne, download() :
- Envoie une requête par lots pour l’opération
download - Reçoit une URL de téléchargement du serveur
- 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
$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
$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
$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
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
{
"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 :
$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.
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 :
$pointer->oid; // string: SHA-256 hash (hex)
$pointer->size; // int: file size in bytes
$pointer->version; // string: spec version URL