Git LFS
Git 대용량 파일 저장소(LFS)는 저장소의 대용량 파일을 작은 포인터 파일로 대체합니다. 실제 파일 내용은 별도의 LFS 서버에 저장됩니다. Pitmaster은 LFS 포인터를 파싱하고 생성하며 일괄 API를 통해 LFS 서버와 통신할 수 있습니다. .gitattributes을 읽거나 LFS clean 및 smudge 필터를 실행하지는 않습니다. add()는 파일을 그대로 저장하고, checkout()는 포인터 파일을 씁니다. 아래와 같이 실제 콘텐츠를 가져오는 작업은 사용자 코드에서 처리해야 합니다.
LFS 포인터 파일
파일을 LFS로 추적하면 git은 실제 콘텐츠 대신 포인터 파일을 저장합니다:
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345
포인터에는 다음 정보가 들어 있습니다:
version: LFS 사양 버전 URLoid: 실제 파일 콘텐츠의 SHA-256 해시(앞에sha256:가 붙음)size: 실제 파일 크기(바이트)
포인터 파싱
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()은 version, oid sha256: 또는 size 줄이 없으면 null을 반환합니다. 값은 검증하지 않으므로 16진수가 아닌 OID나 숫자가 아닌 크기도 허용됩니다.
포인터 감지
if (LfsPointer::isPointer($blobContent)) {
// This blob is an LFS pointer, not actual file content
$pointer = LfsPointer::parse($blobContent);
}
isPointer()은 전체 파싱 없이 버전 URL, oid sha256: 및 size 필드가 있는지 확인합니다.
포인터 생성
// 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
포인터 직렬화
$content = $pointer->serialize();
// "version https://git-lfs.github.com/spec/v1\noid sha256:4d7a21...\nsize 12345\n"
LFS 클라이언트
LfsClient는 일괄 API을 사용해 LFS 서버와 통신합니다(<url>/objects/batch로 POST).
클라이언트 생성
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);
fromRemoteUrl() 메서드는 표준 관례를 따릅니다. git URL에 /info/lfs을 덧붙이며, 없으면 .git을 추가합니다. HTTP(S) 원격 URL만 사용할 수 있는 LFS URL을 제공합니다. SCP 형식의 SSH 원격(git@host:repo.git)은 사용할 수 없습니다.
LfsClient는 PHP의 HTTP 스트림 래퍼를 사용합니다(allow_url_fopen을 활성화해야 함). 인증 정보를 보내지 않고 각 작업과 함께 반환되는 header 맵을 무시하므로 인증이 필요 없는 LFS 서버 및 저장소 URL에서만 작동합니다. 저장소 핸들에 연결되지 않으므로 Pitmaster::processFreeOptions()로 연 저장소에서 작업할 때도 네트워크 요청을 보냅니다.
파일 다운로드
// Single file download
$content = $client->download($pointer->oid, $pointer->size);
file_put_contents('large-file.bin', $content);
내부적으로 download()는 다음 작업을 수행합니다:
download작업에 대한 일괄 요청을 보냅니다- 서버에서 다운로드 URL를 받습니다
- 해당 URL에서 파일 콘텐츠를 가져옵니다
다운로드한 바이트는 OID 또는 크기와 대조해 확인하지 않습니다.
일괄 다운로드
$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']
}
}
파일 업로드
$client->upload($pointer->oid, $pointer->size, $fileContent);
업로드는 upload 작업에 대한 일괄 요청을 보냅니다. 서버에 객체가 이미 있으면 업로드 URL를 반환하지 않으므로 아무 작업도 하지 않습니다. 업로드 요청 자체의 결과는 확인하지 않으므로 업로드 실패가 보고되지 않고 verify 작업도 호출되지 않습니다.
일괄 업로드
$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
}
일괄 API 프로토콜
LFS 일괄 API은 HTTP를 통해 JSON을 사용합니다:
요청
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}
]
}
응답
{
"objects": [
{
"oid": "abc123...",
"size": 12345,
"actions": {
"download": {
"href": "https://storage.example.com/abc123...",
"header": {
"Authorization": "Bearer token"
}
}
}
}
]
}
저장소 작업에 LFS 통합
LFS를 사용하는 저장소를 읽을 때 블롭 콘텐츠가 실제 파일 데이터가 아닌 LFS 포인터일 수 있습니다. 실제 콘텐츠를 가져오려면:
$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;
}
오류 처리
일괄 요청이 실패하거나 유효하지 않은 경우, 또는 다운로드 요청이 실패하면 ProtocolException을 발생시킵니다. 서버가 객체의 다운로드 URL를 반환하지 않으면 download()도 서버 오류 메시지 없이 오류를 발생시킵니다. batchDownload() 및 batchUpload()는 객체별 오류를 발생시키지 않고 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";
}
LFS 포인터 속성
LfsPointer 클래스는 읽기 전용입니다:
$pointer->oid; // string: SHA-256 hash (hex)
$pointer->size; // int: file size in bytes
$pointer->version; // string: spec version URL