انتقل إلى المحتوى

Git LFS

يستبدل Git Large File Storage (LFS) الملفات الكبيرة في مستودعك بملفات مؤشرات صغيرة. ويُخزَّن محتوى الملف الفعلي على خادم LFS منفصل. يمكن لـ Pitmaster تحليل مؤشرات LFS وإنشاؤها والتواصل مع خوادم LFS عبر API الدفعي. لا يقرأ .gitattributes ولا يشغّل مرشحي clean وsmudge في LFS: يخزّن 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() من وجود حقول الإصدار 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 إن لم يكن موجودًا). وحدها عناوين URL البعيدة من نوع HTTP(S) توفر URL صالحًا للاستخدام مع LFS؛ أما عناوين SSH البعيدة بصيغة SCP (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، قد يكون محتوى الكائن الثنائي مؤشرات 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