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

كائنات Git

يُخزّن كل شيء في مستودع git ككائن. وهناك أربعة أنواع: الكتل (محتوى الملفات)، والأشجار (قوائم المجلدات)، والالتزامات (لقطات مع بيانات وصفية)، والوسوم (مراجع مشروحة). ويُعرّف كل كائن بتجزئة SHA-1 لمحتواه.

أنواع الكائنات

Blob

تحتوي الكتلة على محتوى الملف الخام دون بيانات وصفية (لا اسم ملف ولا أذونات).

PHP
use Pitmaster\Object\Blob;

// Create from content
$blob = Blob::fromContent('Hello, world!');
echo $blob->content;    // 'Hello, world!'
echo $blob->size();     // 13
echo $blob->id->hex;    // SHA-1 hash
echo $blob->type->value; // 'blob' (the property is ObjectType::Blob)

شجرة

الشجرة هي قائمة دليل. لكل إدخال وضع واسم وتجزئة تشير إلى كتلة أو شجرة فرعية.

PHP
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;

// Read a tree
$tree = $repo->readObject($treeHash);

foreach ($tree->entries as $entry) {
    echo "{$entry->mode} {$entry->name} {$entry->hash->hex}\n";
}

// Look up a specific entry
$entry = $tree->entry('README.md');

// Create a tree (entries already in git's sort order)
$entries = [
    new TreeEntry('100644', 'README.md', $blobId),
    new TreeEntry('100755', 'deploy.sh', $execBlobId),
    new TreeEntry('40000', 'src', $subtreeId),
];

$tree = Tree::fromEntries($entries);

يكتب Tree::fromEntries() الإدخالات بالترتيب المعطى وسلاسل الأوضاع كما أُعطيت تمامًا، لذا رتّبها أولًا واكتب أوضاع المجلدات كـ40000. تنتج الشجرة المكتوبة باستخدام 040000 أو بترتيب آخر تجزئة تختلف عن تجزئة git، ويعرض git fsck --strict الوضع بأصفار بادئة.

TreeEntry

TreeEntry كائن قيمة للقراءة فقط:

PHP
use Pitmaster\Object\TreeEntry;

$entry->mode;   // '100644', '100755', '40000', '120000', '160000'
$entry->name;   // Filename
$entry->hash;   // ObjectId

// Type checks
$entry->isBlob();        // regular or executable file (mode 10xxxx)
$entry->isExecutable();  // mode 100755
$entry->isTree();        // mode 40000 (also accepts 040000)
$entry->isSymlink();     // mode 120000
$entry->isGitlink();     // mode 160000 (submodule)

قيم الوضع:

الوضع المعنى
100644 ملف عادي
100755 ملف قابل للتنفيذ
40000 مجلد (شجرة)؛ يعرضه git ls-tree بالشكل 040000
120000 رابط رمزي
160000 رابط Git (وحدة فرعية)

تُرتب إدخالات الشجرة حسب الاسم، مع التعامل مع أسماء المجلدات كما لو كانت تنتهي بـ/ لأغراض المقارنة.

إيداع

يربط الالتزام شجرةً بسجلها التاريخي وبياناتها الوصفية.

PHP
use Pitmaster\Object\Commit;

$commit = $repo->head();

$commit->id;         // ObjectId
$commit->tree;       // ObjectId of the tree
$commit->parents;    // array of ObjectId (zero for root, one for normal, two+ for merge)
$commit->author;     // 'Name <email> timestamp timezone'
$commit->committer;  // 'Name <email> timestamp timezone'
$commit->message;    // Full commit message

$commit->isMerge();  // true if two or more parents

إنشاء محتوى الالتزام

PHP
$content = Commit::buildContent(
    tree: $treeId,
    parents: [$parentId],
    author: 'Jane <jane@example.com> 1700000000 +0000',
    committer: 'Jane <jane@example.com> 1700000000 +0000',
    message: "Add feature\n",
);

تنسيق الالتزام الخام:

الكود
tree <hex hash>\n
parent <hex hash>\n       (zero or more)
author <name> <<email>> <timestamp> <tz>\n
committer <name> <<email>> <timestamp> <tz>\n
\n
<message>

وسم

الوسوم المشروحة كائنات تتضمن بيانات وصفية.

PHP
use Pitmaster\Object\Tag;

$tag = $repo->readObject($tagHash);

$tag->object;        // ObjectId of the target
$tag->objectType;    // ObjectType enum (ObjectType::Commit, ...)
$tag->name;          // Tag name
$tag->tagger;        // 'Name <email> timestamp timezone'
$tag->message;       // Tag message

تنسيق الوسم الخام:

الكود
object <hex hash>\n
type commit\n
tag <name>\n
tagger <name> <<email>> <timestamp> <tz>\n
\n
<message>

ObjectId

ObjectId كائن قيمة للقراءة فقط يمثل تجزئة SHA-1 (أو SHA-256).

PHP
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;

// From hex string
$id = ObjectId::fromHex('a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2');

// From binary (20 bytes for SHA-1, 32 for SHA-256)
$id = ObjectId::fromBinary($rawBytes);

// Compute from content
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!');
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!', 'sha256');

// Zero hash
$id = ObjectId::zero();
$id = ObjectId::zero('sha256');

// Properties
$id->hex;      // 40-character hex string (64 for SHA-256)
$id->binary;   // 20-byte raw binary (32 for SHA-256)
$id->algo;     // 'sha1' or 'sha256'
$id->isSha256();
$id->isZero();

// Comparison
$id->equals($otherId);

تُحسب التجزئة على البيانات غير المضغوطة: sha1("{type} {size}{content}")، أو SHA-256 للبايتات نفسها. ويختار fromHex() وfromBinary() الخوارزمية بحسب الطول.

تعداد ObjectType

PHP
use Pitmaster\Object\ObjectType;

ObjectType::Blob;    // 'blob'
ObjectType::Tree;    // 'tree'
ObjectType::Commit;  // 'commit'
ObjectType::Tag;     // 'tag'

تنسيق الكائنات المنفصلة

تُخزّن الكائنات المنفصلة كملفات مستقلة في .git/objects/، وتُنظّم وفق أول محرفين سداسييْن عشريين من تجزئتها.

الكود
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

يُضغط محتوى الملف باستخدام zlib. ويكون المحتوى بعد فك الضغط:

الكود
<type> <decimal size>\0<raw content>

على سبيل المثال، تُخزّن كتلة تحتوي على "Hello" بالشكل التالي:

الكود
zlib_encode("blob 5\0Hello")

قراءة الكائنات المنفصلة

PHP
use Pitmaster\Storage\LooseObjectStore;

$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);

يقوم LooseObjectStore بما يلي:

  1. يحسب مسار الملف انطلاقًا من التجزئة
  2. يقرأ الملف ويفك ضغطه باستخدام zlib
  3. يحلل الترويسة لاستخراج النوع والحجم
  4. يفوّض المهمة إلى المحلل المناسب (Blob وTree وCommit وTag)

كتابة الكائنات المنفصلة

PHP
$store->write($object);

خطوات الكتابة:

  1. التسلسل: "{type} {size}\0{content}"
  2. استخدام id الخاص بالكائن، المحسوب عند إنشائه (ويتحقق من التجزئة عند القراءة)
  3. الضغط: zlib_encode(serialized)
  4. الكتابة إلى .git/objects/{hash[0..1]}/{rest of hash}
  5. كتابة ذرية (الكتابة إلى ملف مؤقت ثم إعادة تسميته)

ObjectSerializer

يتولى ObjectSerializer ترميز تنسيق الكائنات على السلك وفك ترميزه.

PHP
use Pitmaster\Storage\ObjectSerializer;

// Encode
$raw = ObjectSerializer::encodeRaw($object);
// "blob 5\0Hello"
$compressed = ObjectSerializer::encode($object);
// zlib-compressed form, as stored in a loose object file

// Decode
$object = ObjectSerializer::decodeRaw($raw);
$object = ObjectSerializer::decode($compressed, $expectedHex); // throws CorruptObjectException on a hash mismatch

ObjectDatabase

ObjectDatabase مخزن مركّب يبحث أولًا عن الكائنات المنفصلة، ثم عن ملفات الحزم.

PHP
use Pitmaster\Storage\ObjectDatabase;

$db = new ObjectDatabase('/path/to/.git/objects');

$object = $db->read($objectId);    // Search loose, then packs
$db->write($object);               // Always writes to loose
$exists = $db->exists($objectId);  // Check loose, then packs
$all = $db->listAll();             // All hashes from loose + packs

الكتابة مباشرةً إلى التخزين المنفصل مقصودة. اترك git gc يعيد تجميع الكائنات في ملفات الحزم.

دعم SHA-256

يجرّد Pitmaster خوارزمية التجزئة عبر ObjectId. وSHA-1 هو الافتراضي. ويُختار SHA-256 لكل مستودع عند إنشائه:

PHP
$repo = Pitmaster::init('/path/to/project', 'sha256');
سطر الأوامر
./vendor/bin/pitmaster init --object-format=sha256

يكتب هذا extensions.objectformat = sha256 في الإعدادات. وتقرأ المستودعات التي تُفتح لاحقًا التنسيق من هذا المدخل، ثم تستخدم كل عمليات الكائنات والأشجار والفهرس والمراجع سلاسل سداسية عشرية بطول 64 محرفًا وتجزئات ثنائية بطول 32 بايت. لا يوجد إعداد عام أو ثابت للخوارزمية.

تتلقى فئات الكائنات الخوارزمية كوسيط عند إنشائك الكائنات بنفسك: Blob::fromContent($content, 'sha256') وTree::fromEntries($entries, 'sha256') وObjectId::compute($type, $content, 'sha256'). وينشئ Pitmaster::clone() مستودعات SHA-1.

التنسيق الثنائي لإدخال الشجرة

يُرمّز كل إدخال داخل محتوى كائن الشجرة كما يلي:

الكود
<octal mode as ASCII> <space> <filename> \0 <binary hash: 20 bytes SHA-1, 32 bytes SHA-256>

ملاحظة: التجزئة ثنائية خام وليست سداسية عشرية، بخلاف التجزئات السداسية العشرية في كائنات الالتزام والوسم.

تُرتب الإدخالات بمقارنة الأسماء كما لو كان للمجلدات لاحقة /. ويجب الالتزام بترتيب الفرز هذا بدقة كي تطابق تجزئة الشجرة ما يحسبه git.