Към съдържанието

Git LFS

Git Large File Storage (LFS) заменя големите файлове в хранилището ви с малки файлове-указатели. Действителното съдържание се съхранява на отделен LFS сървър. Pitmaster може да парсва и създава LFS указатели и да комуникира с LFS сървъри чрез пакетния API. Не чете .gitattributes и не изпълнява LFS филтрите clean и smudge: add() съхранява файловете без промени, а checkout() записва файлове-указатели. Извличането на действителното съдържание остава задача на вашия код, както е показано по-долу.

Файлове-указатели LFS

Когато файлът се проследява от LFS, git съхранява файл-указател вместо действителното съдържание:

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

Указателят съдържа:

  • version: версията на спецификацията LFS URL
  • oid: хеш SHA-256 на действителното съдържание на файла (с префикс sha256:)
  • size: действителният размер на файла в байтове

Парсване на указатели

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() връща null, ако липсва редът version, oid sha256: или size. Стойностите не се валидират (приемат се не-шестнадесетичен OID и нечислов размер).

Откриване на указатели

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

isPointer() проверява дали присъстват полетата version URL, oid sha256: и size, без да парсва изцяло.

Създаване на указатели

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

Сериализиране на указатели

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

LFS клиент

LfsClient комуникира с LFS сървъри чрез пакетния API (POST към <url>/objects/batch).

Създаване на клиент

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

Методът fromRemoteUrl() следва стандартната конвенция: добавя /info/lfs към git URL (като добавя .git, ако липсва). Само отдалечените адреси HTTP(S) дават използваем LFS URL; отдалечените адреси във формат SCP SSH (git@host:repo.git) не работят.

LfsClient използва обвивката за поток HTTP на PHP (трябва да е активирана allow_url_fopen). Не изпраща идентификационни данни и игнорира картата header, върната с всяко действие, затова работи само с LFS сървъри и URL адреси за съхранение, които не изискват удостоверяване. Не е свързан с манипулация на хранилище, затова изпраща мрежови заявки дори при работа с хранилище, отворено с Pitmaster::processFreeOptions().

Изтегляне на файлове

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

На практика download():

  1. Изпраща пакетна заявка за операцията download
  2. Получава URL за изтегляне от сървъра
  3. Извлича съдържанието на файла от този URL

Изтеглените байтове не се проверяват спрямо OID или размера.

Пакетно изтегляне

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

Качване на файлове

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

При качване се изпраща пакетна заявка за операцията upload. Ако сървърът вече има обекта, той не връща URL за качване и операцията не прави нищо. Резултатът от самата заявка за качване не се проверява, затова неуспешното качване не се съобщава и действието verify не се извиква.

Пакетно качване

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
}

Пакетен протокол API

Пакетният API на LFS използва JSON през HTTP:

Заявка

Код
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}
    ]
}

Отговор

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

Интегриране на LFS с операциите на хранилището

При четене на хранилище, което използва LFS, съдържанието на blob може да е указател към LFS, а не действителните данни на файла. За да получите действителното съдържание:

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

Обработка на грешки

Неуспешна или невалидна пакетна заявка, както и неуспешна заявка за изтегляне, хвърля ProtocolException. download() също хвърля грешка, когато сървърът не върне URL за изтегляне на обекта, без съобщението за грешка от сървъра. batchDownload() и batchUpload() не хвърлят грешка при грешки за отделни обекти, а ги връщат в полето 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";
}

Свойства на LFS указателите

Класът LfsPointer е само за четене:

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