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

تنسيق الفهرس

ملف الفهرس (.git/index) هو منطقة التجهيز. يسجل الملفات التي ستُضمّن في الالتزام التالي، مع بيانات وصفية لحساب الحالة بكفاءة. يقرأ Pitmaster الإصدارات 2 و3 و4 من الفهرس، ويكتب الإصدارين 2 و3. تؤدي كتابة فهرس قُرئ بالإصدار 4 إلى إنشاء ملف لا يستطيع git قراءته (#15)، لذا حوّل هذه المستودعات باستخدام git update-index --index-version 2 قبل الكتابة عبر Pitmaster.

قراءة الفهرس

PHP
use Pitmaster\Index\Index;

$index = Index::open('/path/to/.git/index');   // pass 32 as the second argument for SHA-256

// Or through the repository
$index = $repo->index();

// Entry count (all stages)
echo $index->count();

// Iterate stage 0 entries, keyed by path
foreach ($index->entries() as $path => $entry) {
    echo "{$entry->path} {$entry->hash->hex}\n";
}

// Look up a specific entry
$entry = $index->entry('src/File.php');

يعيد Index::open() فهرسًا فارغًا إن لم يكن الملف موجودًا. يعيد entries() إدخالات المرحلة 0 فقط؛ ويضم allEntries() مراحل التعارض، ويسرد paths() المسارات المتعقبة.

IndexEntry

كل إدخال في الفهرس كائن قيمة للقراءة فقط:

PHP
use Pitmaster\Index\IndexEntry;

$entry->path;          // File path relative to repo root
$entry->hash;          // ObjectId of the blob
$entry->mode;          // File mode as an int (0100644, 0100755, 0120000, 0160000)
$entry->fileSize;      // File size in bytes
$entry->ctimeSec;      // Metadata change time (seconds)
$entry->ctimeNsec;     // Metadata change time nanoseconds
$entry->mtimeSec;      // Modification time (seconds)
$entry->mtimeNsec;     // Modification time nanoseconds
$entry->dev;           // Device number
$entry->ino;           // Inode number
$entry->uid;           // User ID
$entry->gid;           // Group ID
$entry->flags;         // Raw flags field
$entry->extendedFlags; // v3 extended flags (skip-worktree, intent-to-add)
$entry->stage();       // Conflict stage (0 = normal, 1 = base, 2 = ours, 3 = theirs)
$entry->assumeValid(); // Assume-valid bit
$entry->skipWorktree(); // Skip-worktree bit

إنشاء الإدخالات

PHP
// From file stat
$entry = IndexEntry::fromStat('src/File.php', $blobId, '/full/path/to/src/File.php');

// Minimal (no stat info)
$entry = IndexEntry::create('src/File.php', $blobId);

// With explicit mode
$entry = IndexEntry::create('deploy.sh', $blobId, 0100755);

تعديل الفهرس

PHP
// Add or replace an entry
$index->addEntry($entry);

// Remove an entry
$index->removeEntry('src/OldFile.php');

// Replace all stages of a conflicted path with one resolved entry
$index->resolveConflict('src/File.php', $entry);

يتعامل كل من addEntries() وremoveEntries() مع عدة إدخالات أو مسارات في تمريرة واحدة. أي تغيير في الإدخالات يحذف الامتدادات المخزنة (انظر أدناه).

كتابة الفهرس

PHP
use Pitmaster\Index\IndexWriter;

IndexWriter::write($index, '/path/to/.git/index');

أو عبر المستودع (الذي يتولى ذلك تلقائيًا):

PHP
$repo->add('src/File.php');    // Reads file, creates blob, updates index, writes
$repo->remove('--cached', 'old-file.txt'); // Removes from index only, writes

التنسيق الثنائي

الكود
Signature:   DIRC (4 bytes, ASCII)
Version:     4 bytes, big-endian (2, 3, or 4)
Entry count: 4 bytes, big-endian

تنسيق الإدخال (v2)

كل إدخال هو:

الكود
ctime seconds:     4 bytes, big-endian
ctime nanoseconds: 4 bytes, big-endian
mtime seconds:     4 bytes, big-endian
mtime nanoseconds: 4 bytes, big-endian
dev:               4 bytes, big-endian
ino:               4 bytes, big-endian
mode:              4 bytes, big-endian
uid:               4 bytes, big-endian
gid:               4 bytes, big-endian
file size:         4 bytes, big-endian
object hash:       20 bytes SHA-1 or 32 bytes SHA-256 (raw binary)
flags:             2 bytes, big-endian
path:              variable length, NUL-terminated
padding:           1-8 NUL bytes to align to 8-byte boundary

حقل الأعلام:

  • البت 15: علم الافتراض بصحة الملف
  • البتان 13-12: مرحلة الدمج (0-3)
  • البتات 11-0: طول اسم المسار، أو 0xFFF عندما يبلغ طول المسار 4095 بايت أو أكثر (عندها يبحث القارئ عن NUL)

اختلافات الإصدارات

الإصدار 2: التنسيق الأساسي الموضح أعلاه. وهو الأكثر شيوعًا.

الإصدار 3: يضيف أعلامًا موسعة (بايتان إضافيان بعد الأعلام العادية). يدعم البتين skip-worktree وintent-to-add. ويُستخدم عندما يحتاج أي إدخال إلى أعلام موسعة.

الإصدار 4: يضيف ضغط بادئة المسار. فبدلًا من تخزين المسارات كاملة، يخزن كل إدخال عدد البايتات التي يجب حذفها من مسار الإدخال السابق، ثم اللاحقة الجديدة. يقلل ذلك حجم الفهرس بشكل كبير في الأشجار المتداخلة بعمق.

مراحل التعارض

أثناء تعارض الدمج، يمكن أن يكون للمسار الواحد إدخالات متعددة في الفهرس بمراحل مختلفة:

المرحلة المعنى
0 عادي (دون تعارض)
1 الإصدار الأساسي (السلف المشترك)
2 نسختنا (الفرع الحالي)
3 نسختهم (الفرع الجاري دمجه)
PHP
// During a conflict, entries have non-zero stages
foreach ($index->allEntries() as $entry) {
    if ($entry->stage() > 0) {
        echo "Conflict: {$entry->path} (stage {$entry->stage()})\n";
    }
}

$index->hasUnmerged();            // true while conflicts remain
$index->unmergedPaths();          // conflicted paths
$index->stageEntries('a.txt');    // [1 => base, 2 => ours, 3 => theirs]

امتدادات الفهرس

تُضاف الامتدادات بعد الإدخالات وقبل المجموع الاختباري. ويتكون كل امتداد من توقيع بطول 4 بايت وحجم بطول 4 بايت وبيانات متغيرة الطول.

يحتفظ Pitmaster بكتل الامتداد كبايتات خام (يعيد $index->extensions() أزواج signature وdata)، ويكتبها دون تغيير عندما لا تتغير الإدخالات. ولا يفسر أي امتداد أو ينشئه؛ وأي تغيير في الإدخالات يحذفها كلها، وهو ما يقبله git ويعيد بناءها عند الحاجة. تُذكر امتدادات git أدناه للرجوع إليها.

امتداد TREE

يخزن مؤقتًا كائنات الأشجار المحسوبة مسبقًا للمجلدات. يسرّع git status وgit commit بتجنب إعادة حساب الأشجار عندما يتغير الفهرس وحده.

التوقيع: TREE (0x54524545)

امتداد REUC

يسجل معلومات التراجع عن الحل. بعد حل التعارض، تُنقل إدخالات المراحل 1/2/3 الأصلية إلى هنا كي يمكن استعادتها باستخدام git checkout -m.

التوقيع: REUC (0x52455543)

امتدادات أخرى

يكتب git أيضًا:

  • EOIE (نهاية إدخال الفهرس): يحدد موضع نهاية الإدخالات وبدء الامتدادات
  • IEOT (جدول إزاحات إدخالات الفهرس): يتيح تحميل الفهرس بالتوازي
  • link (الفهرس المقسّم): للمستودعات الكبيرة التي تستخدم فهرسًا مقسّمًا

في تنسيق git، تكون الامتدادات التي يبدأ توقيعها بحرف كبير اختيارية، والتي يبدأ توقيعها بحرف صغير مطلوبة. يقبل Pitmaster كلا النوعين دون تحقق. ولا يُحمّل الفهرس المقسّم (link)، لذا لا تُدعم المستودعات التي تستخدمه.

المجموع الاختباري

ينتهي ملف الفهرس بمجموع اختباري لجميع البايتات السابقة باستخدام تجزئة المستودع (20 بايت SHA-1، و32 بايت SHA-256). يكتبه Pitmaster عند الحفظ، لكنه لا يتحقق منه عند القراءة.

يرمي كل من Index::open() وIndex::parse() استثناء IndexParseException عند غياب توقيع DIRC أو وجود إصدار غير مدعوم.

الفهرس والحالة

الفهرس محوري في حساب الحالة. يقارن WorkingTreeStatus بين:

  1. شجرة HEAD والفهرس: لاكتشاف التغييرات المُضافة إلى الفهرس
  2. الفهرس وشجرة العمل: لاكتشاف التغييرات غير المُضافة إلى الفهرس

تتيح حقول stat (ctime وmtime وdev وino وsize وmode وuid وgid) إجراء فحوص سريعة لمعرفة «هل تغير هذا الملف؟» دون قراءة محتواه. إذا تطابقت حقول stat وكان آخر تعديل للملف قبل بدء فحص الحالة، فيُفترض أنه لم يتغير. ويُعد اختلاف الحجم علامة فورية على تغيّره. وإلا يقرأ Pitmaster الملف ويحسب تجزئته للتحقق من التغييرات الفعلية في المحتوى.

علم الافتراض بصحة الملف

PHP
if ($entry->assumeValid()) {
    // Entry has assume-valid set (flags & 0x8000)
}

يطلب هذا العلم من git تخطي فحص الملف أثناء حساب الحالة، حتى إذا تغيرت حقول stat (git update-index --assume-unchanged). يحتفظ Pitmaster بهذا البت عند إعادة كتابة الإدخالات، لكن حساب الحالة فيه لا يراعيه ويواصل مقارنة الملف.