Git LFS
Git Large File Storage (LFS) reemplaza los archivos grandes del repositorio por pequeños archivos de puntero. El contenido real se almacena en un servidor LFS independiente. Pitmaster puede analizar y crear punteros LFS y comunicarse con servidores LFS mediante el API por lotes. No lee .gitattributes ni ejecuta los filtros clean y smudge de LFS: add() almacena los archivos tal cual y checkout() escribe archivos de puntero. Tu código debe obtener el contenido real, como se muestra a continuación.
Archivos de puntero LFS
Cuando un archivo está bajo seguimiento de LFS, git almacena un archivo de puntero en vez del contenido real:
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345
El puntero contiene:
version: la versión de la especificación LFS URLoid: hash SHA-256 del contenido real del archivo (con el prefijosha256:)size: el tamaño real del archivo en bytes
Analizar punteros
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() devuelve null si falta la línea version, oid sha256: o size. No valida los valores (se aceptan un OID no hexadecimal o un tamaño no numérico).
Detectar punteros
if (LfsPointer::isPointer($blobContent)) {
// This blob is an LFS pointer, not actual file content
$pointer = LfsPointer::parse($blobContent);
}
isPointer() comprueba la presencia de los campos de versión URL, oid sha256: y size sin realizar un análisis completo.
Crear punteros
// 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
Serializar punteros
$content = $pointer->serialize();
// "version https://git-lfs.github.com/spec/v1\noid sha256:4d7a21...\nsize 12345\n"
Cliente LFS
El LfsClient se comunica con los servidores LFS mediante el API por lotes (POST a <url>/objects/batch).
Crear un cliente
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);
El método fromRemoteUrl() sigue la convención estándar: añade /info/lfs al URL de git (añade .git si no está presente). Solo las URL de remotos HTTP(S) proporcionan un URL LFS utilizable; los remotos de estilo SCP SSH (git@host:repo.git) no.
LfsClient usa el contenedor de flujos HTTP de PHP (debe estar habilitado allow_url_fopen). No envía credenciales e ignora el mapa header que devuelve cada acción, por lo que solo funciona con servidores LFS y URL de almacenamiento que no requieren autenticación. No está vinculado a un identificador de repositorio, así que realiza solicitudes de red incluso si trabajas con un repositorio abierto con Pitmaster::processFreeOptions().
Descargar archivos
// Single file download
$content = $client->download($pointer->oid, $pointer->size);
file_put_contents('large-file.bin', $content);
En segundo plano, download():
- Envía una solicitud por lotes para la operación
download - Recibe del servidor una URL de descarga
- Obtiene el contenido del archivo desde esa URL
Los bytes descargados no se verifican con el OID ni con el tamaño.
Descarga por lotes
$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']
}
}
Subir archivos
$client->upload($pointer->oid, $pointer->size, $fileContent);
La carga envía una solicitud por lotes para la operación upload. Si el servidor ya tiene el objeto, no devuelve ninguna URL de carga y la operación no hace nada. El resultado de la solicitud de carga no se comprueba, por lo que los errores de carga no se notifican y no se llama a la acción verify.
Carga por lotes
$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
}
Protocolo de API por lotes
El API por lotes de LFS usa JSON sobre HTTP:
Solicitud
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}
]
}
Respuesta
{
"objects": [
{
"oid": "abc123...",
"size": 12345,
"actions": {
"download": {
"href": "https://storage.example.com/abc123...",
"header": {
"Authorization": "Bearer token"
}
}
}
}
]
}
Integrar LFS con las operaciones del repositorio
Al leer un repositorio que usa LFS, el contenido de los blobs puede ser punteros LFS en vez de los datos reales del archivo. Para obtener el contenido real:
$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;
}
Gestión de errores
Una solicitud por lotes fallida o no válida, y una solicitud de descarga fallida, generan ProtocolException. download() también genera un error cuando el servidor no devuelve ninguna URL de descarga para el objeto, sin incluir el mensaje de error del servidor. batchDownload() y batchUpload() no generan errores por errores de objetos individuales; los devuelven en el campo 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";
}
Propiedades de los punteros LFS
La clase LfsPointer es de solo lectura:
$pointer->oid; // string: SHA-256 hash (hex)
$pointer->size; // int: file size in bytes
$pointer->version; // string: spec version URL