Git LFS
Git Large File Storage (LFS) substitui arquivos grandes no repositório por pequenos arquivos de ponteiro. O conteúdo real fica armazenado em um servidor LFS separado. Pitmaster pode analisar e criar ponteiros LFS e se comunicar com servidores LFS pelo API em lote. Não lê .gitattributes nem executa os filtros clean e smudge do LFS: add() armazena os arquivos como estão e checkout() grava arquivos de ponteiro. Cabe ao seu código obter o conteúdo real, como mostrado abaixo.
Arquivos de ponteiro LFS
Quando um arquivo é rastreado pelo LFS, git armazena um arquivo de ponteiro em vez do conteúdo real:
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345
O ponteiro contém:
version: a versão da especificação LFS URLoid: hash SHA-256 do conteúdo real do arquivo (com o prefixosha256:)size: tamanho real do arquivo em bytes
Analisar ponteiros
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() retorna null quando falta a linha version, oid sha256: ou size. Não valida os valores (um OID não hexadecimal ou um tamanho não numérico é aceito).
Detectar ponteiros
if (LfsPointer::isPointer($blobContent)) {
// This blob is an LFS pointer, not actual file content
$pointer = LfsPointer::parse($blobContent);
}
isPointer() verifica a presença dos campos de versão URL, oid sha256: e size sem fazer uma análise completa.
Criar ponteiros
// 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 ponteiros
$content = $pointer->serialize();
// "version https://git-lfs.github.com/spec/v1\noid sha256:4d7a21...\nsize 12345\n"
Cliente LFS
O LfsClient se comunica com servidores LFS usando o API em lote (POST para <url>/objects/batch).
Criar um 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);
O método fromRemoteUrl() segue a convenção padrão: acrescenta /info/lfs à git URL (adicionando .git se não estiver presente). Apenas URLs remotas HTTP(S) fornecem uma URL LFS utilizável; remotos no estilo SCP SSH (git@host:repo.git) não funcionam.
LfsClient usa o wrapper de stream HTTP de PHP (allow_url_fopen deve estar ativado). Não envia credenciais e ignora o mapa header retornado com cada ação, por isso só funciona com servidores LFS e URLs de armazenamento que não exigem autenticação. Não está vinculado a um identificador de repositório, então faz solicitações de rede mesmo ao trabalhar com um repositório aberto com Pitmaster::processFreeOptions().
Baixar arquivos
// Single file download
$content = $client->download($pointer->oid, $pointer->size);
file_put_contents('large-file.bin', $content);
Nos bastidores, download():
- Envia uma solicitação em lote para a operação
download - Recebe do servidor uma URL de download
- Busca o conteúdo do arquivo nessa URL
Os bytes baixados não são verificados em relação ao OID ou ao tamanho.
Download em lote
$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']
}
}
Enviar arquivos
$client->upload($pointer->oid, $pointer->size, $fileContent);
O envio faz uma solicitação em lote para a operação upload. Se o servidor já tiver o objeto, não retorna URL de envio e a operação não faz nada. O resultado da própria solicitação de envio não é verificado; portanto, uma falha no envio não é informada e a ação verify não é chamada.
Upload em lote
$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 API em lote
O API em lote do LFS usa JSON sobre HTTP:
Solicitação
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}
]
}
Resposta
{
"objects": [
{
"oid": "abc123...",
"size": 12345,
"actions": {
"download": {
"href": "https://storage.example.com/abc123...",
"header": {
"Authorization": "Bearer token"
}
}
}
}
]
}
Integração do LFS com operações do repositório
Ao ler um repositório que usa LFS, o conteúdo dos blobs pode ser composto por ponteiros LFS em vez dos dados reais dos arquivos. Para obter o conteúdo 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;
}
Tratamento de erros
Uma solicitação em lote com falha ou inválida, assim como uma solicitação de download com falha, lança ProtocolException. download() também lança uma exceção se o servidor não retornar uma URL de download para o objeto, sem incluir a mensagem de erro do servidor. batchDownload() e batchUpload() não lançam exceções para erros por objeto; eles os retornam no 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";
}
Propriedades do ponteiro LFS
A classe LfsPointer é somente para leitura:
$pointer->oid; // string: SHA-256 hash (hex)
$pointer->size; // int: file size in bytes
$pointer->version; // string: spec version URL