
# ملفات الحزم

تستخدم git ملفات الحزم للتخزين والنقل بكفاءة. فبدلًا من تخزين كل كائن في ملف مستقل مضغوط باستخدام zlib، تجمع ملفات الحزم كائنات كثيرة في ملف واحد مع ضغط اختياري للفروق. وتستخدم ملفات الحزم جميع المستودعات المستنسخة تقريبًا.

## بنية ملف الحزمة

يحتوي ملف الحزمة (`.git/objects/pack/pack-{hash}.pack`) على:

```
Header:   PACK (4 bytes) + version (4 bytes, big-endian) + count (4 bytes, big-endian)
Entries:  [type+size varint] [delta-base] [zlib-compressed data] ...
Trailer:  checksum of the pack (20 bytes SHA-1, 32 bytes SHA-256)
```

### ترميز نوع الكائن

يبدأ كل إدخال بعدد صحيح متغير الطول تتداخل فيه قيمة النوع والحجم:

```
First byte: 1TTTSSSS
  TTT  = object type (1-4 for base objects, 6 for OFS_DELTA, 7 for REF_DELTA)
  SSSS = first 4 bits of uncompressed size
  MSB  = 1 means more size bytes follow
```

قيم النوع:
| القيمة | النوع |
|-------|------|
| 1 | التزام |
| 2 | شجرة |
| 3 | كتلة |
| 4 | وسم |
| 6 | OFS_DELTA (فرق قائم على الإزاحة) |
| 7 | REF_DELTA (فرق قائم على التجزئة) |

## قراءة ملفات الحزم

```php
use Pitmaster\Pack\PackFile;

$pack = PackFile::open(
    '/path/to/.git/objects/pack/pack-abc123.pack',
    '/path/to/.git/objects/pack/pack-abc123.idx',
);

$pack->has($hex);               // Is the object in this pack?
$object = $pack->read($hex);    // Resolved GitObject, or null
$object = $pack->readAtOffset($offset);
$hashes = $pack->allHashes();
```

الصنف `PackFile`:
1. يتحقق من ترويسة `PACK` ورقم الإصدار
2. يعثر على الإزاحة عبر ملف `.idx` ويحلل قيمة varint للنوع/الحجم الخاصة بالمدخل
3. يفك ضغط البيانات المضغوطة باستخدام zlib
4. للعناصر الأساسية: يعيد المحتوى مباشرةً
5. لعناصر دلتا: يحل العنصر الأساسي (باتباع السلاسل) ويطبق دلتا، مع تخزين العناصر المحلولة مؤقتًا

## ملفات فهرس الحزم

يقابل كل ملف `.pack` ملف `.idx` للبحث السريع. من دون الفهرس، يتطلب العثور على عنصر فحص الحزمة بأكملها.

### تنسيق الفهرس v2

```
Magic:    FF 74 4F 63 (identifies v2)
Version:  00 00 00 02
Fanout:   256 x 4-byte big-endian counts (cumulative)
Names:    N x 20-byte SHA-1 hashes, sorted (32 bytes for SHA-256)
CRC32s:   N x 4-byte CRC32 checksums
Offsets:  N x 4-byte pack offsets (MSB=1 means large offset table)
Large:    M x 8-byte offsets (for packs > 2GB)
Checksums: pack checksum + index checksum (20 bytes each for SHA-1)
```

```php
use Pitmaster\Pack\PackIndex;

$index = PackIndex::open('/path/to/pack-abc123.idx');

// Look up an object
$offset = $index->findOffset($hex);  // Pack file offset, or null

// List all objects in the pack
$hashes = $index->allHashes();
$count = $index->objectCount();
```

يتيح جدول التوزيع بحثًا ثنائيًا O(log n): تشير الإدخالات الـ256 إلى عدد التجزئات التي يبدأ كل منها بأحد البايتات الأولى الممكنة.

### تنسيق الفهرس v1

تنسيق أقدم لا يتضمن مجاميع تحقق CRC32. يدعم Pitmaster كليهما: يقرأ `PackIndex::open()` ملف v1 عند غياب قيمة التمييز السحرية لـv2، لذا يعمل الكود نفسه مع أي منهما.

## حل دلتا

تخزن العناصر ذات الدلتا تعليمات لإعادة بناء المحتوى من عنصر أساسي بدلًا من تخزين المحتوى كاملًا. وهذا يحقق ضغطًا كبيرًا للعناصر المتشابهة (e.g، مثل الإصدارات المتتالية للملف نفسه).

### أنواع دلتا

**OFS_DELTA** (النوع 6): يُحدَّد العنصر الأساسي بإزاحة سالبة داخل ملف الحزمة نفسه. وهو الأكثر شيوعًا لأنه لا يتطلب معرفة تجزئة العنصر الأساسي.

**REF_DELTA** (النوع 7): يُحدَّد العنصر الأساسي بتجزئته. في Git، قد يكون العنصر الأساسي خارج الحزمة (حزم رقيقة). يبحث Pitmaster عن عناصر REF_DELTA الأساسية داخل الحزمة نفسها فقط، لذا لا يدعم الحزم الرقيقة ([#16](https://github.com/inline0/pitmaster/issues/16)).

### تعليمات دلتا

محتوى دلتا عبارة عن سلسلة من تعليمات النسخ والإدراج:

```
Copy:   1XXXXXXX [offset bytes] [size bytes]   (copy from base)
Insert: 0LLLLLLL <L bytes of literal data>       (insert new data)
```

تعليمات النسخ: تشير بتات L السبعة X إلى بايتات الإزاحة والحجم الموجودة. يصل طول الإزاحة إلى 4 بايتات، والحجم إلى 3 بايتات. إذا كان الحجم صفرًا، فهذا يعني 0x10000 (65536) بايت.

تعليمات الإدراج: تحدد بتات L السبعة طول البيانات الحرفية (1-127 بايتًا). القيمة L=0 محجوزة/غير صالحة.

### DeltaApplier

```php
use Pitmaster\Pack\DeltaApplier;

$result = DeltaApplier::apply($baseContent, $deltaInstructions);
```

`DeltaApplier`:
1. يقرأ الحجم الأساسي المتوقع وحجم النتيجة من ترويسة دلتا (بترميز LEB128)
2. يتكرر على التعليمات:
   - نسخ: يستخرج الإزاحة والحجم، ثم ينسخ البايتات من المحتوى الأساسي
   - إدراج: يستخرج الطول، ثم ينسخ البايتات الحرفية من بيانات دلتا
3. يتحقق من تطابق حجم النتيجة مع الحجم المتوقع

### سلاسل دلتا

قد يكون العنصر الأساسي لعنصر ذي دلتا نفسه ذا دلتا. يتبع `PackFile::read()` و`readAtOffset()` السلسلة:

1. اقرأ المدخل. إذا كان من نوع أساسي (commit أو tree أو blob أو tag)، فأعِده.
2. إذا كان OFS_DELTA: احسب إزاحة العنصر الأساسي (الإزاحة الحالية ناقص الإزاحة السالبة المشفرة)، واقرأ المدخل الأساسي، ثم تابع تكراريًا.
3. إذا كان REF_DELTA: ابحث عن العنصر الأساسي حسب التجزئة في فهرس الحزمة نفسها، ثم تابع تكراريًا.
4. طبّق تعليمات دلتا على العنصر الأساسي الذي جرى حله.
5. فرض حد `PITMASTER_MAX_DELTA_CHAIN` (القيمة الافتراضية 50) لمنع السلاسل الجامحة.

```php
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);

// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();
```

## PackWriter

يمكن لـPitmaster كتابة ملفات الحزم لعمليات الشبكة (push).

```php
use Pitmaster\Pack\PackWriter;
use Pitmaster\Pack\PackIndexer;

$packData = PackWriter::encode($objects);           // GitObject[] to pack bytes
$paths = PackWriter::write($packDir, $objects);     // ['packPath' => ..., 'idxPath' => ..., 'hash' => ...]

$idxPath = PackIndexer::writeIndex($packPath);      // Build the .idx for a received pack
```

`PackWriter`:
1. يجمع العناصر المراد تضمينها في الحزمة
2. يسلسل كل عنصر مع ترويسة varint للنوع/الحجم
3. يضغط المحتوى باستخدام zlib
4. يكتب ترويسة PACK والمدخلات وذيل SHA-1

يكتب حاليًا العناصر الأساسية فقط (من دون ضغط دلتا). يمكن إضافة ضغط دلتا لاحقًا كتحسين؛ دع `git gc` يعيد التعبئة لتحسين كفاءة التخزين.

## PackEnumerator

كرّر على جميع العناصر في الحزمة بترتيب الفهرس:

```php
use Pitmaster\Pack\PackEnumerator;

$enumerator = new PackEnumerator($packFile); // a PackFile opened with its .idx

foreach ($enumerator->enumerate() as $object) {
    echo "{$object->type->value} {$object->id->hex} {$object->size()}\n";
}
```

كل عنصر هو `GitObject` جرى حله.

## MultiPackIndex

في المستودعات التي تحتوي على ملفات حزم كثيرة، يوفر فهرس الحزم المتعددة (MIDX) عملية بحث واحدة عبر جميع الحزم.

```php
use Pitmaster\Pack\MultiPackIndex;
```

يحتوي ملف MIDX (`.git/objects/pack/multi-pack-index`) على:
- جدول توزيع (مثل فهرس الحزمة v2)
- معرّفات العناصر مرتبة عبر جميع الحزم
- أسماء ملفات الحزم وتعيينات الحزم لكل عنصر
- إزاحات داخل ملفات الحزم الفردية

يحلل `MultiPackIndex::open($path)` ملف MIDX ويوفر `findObject($hex)` و`packNames()`. لا تستخدم عمليات البحث عن العناصر في المستودع هذا الملف؛ إذ تقرأ قاعدة بيانات العناصر دائمًا ملفات `.idx` الفردية.

## CommitGraph

يسرّع ملف رسم بياني للالتزامات اجتياز سجل الالتزامات وحساب أساس الدمج.

```php
use Pitmaster\Pack\CommitGraph;
```

يوجد في `.git/objects/info/commit-graph`، ويحتوي على:
- معرّفات كائنات الالتزام (OID) بترتيب تصاعدي
- معلومات الأصل (مفهرسة ولا تتطلب قراءة العناصر)
- أرقام الأجيال (للاستعلام عن إمكانية الوصول بسرعة أكبر)
- الطوابع الزمنية للالتزامات

عند توفره، تقرأ عملية حساب أساس الدمج خلف `Repository::merge()` و`mergeBase()` و`isBranchMerged()` وفحص التقديم السريع عند الدفع روابط الأصل من رسم بياني للالتزامات بدلًا من قراءة عناصر الالتزام الفردية. ولا تستخدمه `CommitWalker` (السجل).

## الأعداد الصحيحة متغيرة الطول

تستخدم ملفات الحزم عدة ترميزات لأعداد صحيحة متغيرة الطول:

### Git varint (ترويسات النوع/الحجم)

ترميز الاستمرار بالبت الأعلى، حيث يشير البت الأكثر أهمية إلى وجود بايتات أخرى لاحقة:

```php
use Pitmaster\Encoding\VarInt;

$size = VarInt::decodePackSize($reader, $initialBits);  // continues the size from the first header byte
$offset = VarInt::decodeOfsOffset($reader);             // OFS_DELTA base distance
$header = VarInt::encodePackSize($type, $size);
```

### LEB128 (ترويسات دلتا)

يُستخدم في ترويسات تعليمات دلتا للحجم الأساسي وحجم النتيجة:

```php
use Pitmaster\Encoding\Leb128;

$value = Leb128::decodeUnsigned($reader);   // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);
```

### BinaryReader

تدفق بايتات منخفض المستوى مع تتبع الموضع:

```php
use Pitmaster\Encoding\BinaryReader;

$reader = new BinaryReader($data);
$byte = $reader->readByte();
$int = $reader->readUint32();   // big-endian
$bytes = $reader->read(20);
$hash = $reader->readHash(20);
$reader->position();  // Current offset
$reader->seek(0);
```
