كائنات Git
يُخزّن كل شيء في مستودع git ككائن. وهناك أربعة أنواع: الكتل (محتوى الملفات)، والأشجار (قوائم المجلدات)، والالتزامات (لقطات مع بيانات وصفية)، والوسوم (مراجع مشروحة). ويُعرّف كل كائن بتجزئة SHA-1 لمحتواه.
أنواع الكائنات
Blob
تحتوي الكتلة على محتوى الملف الخام دون بيانات وصفية (لا اسم ملف ولا أذونات).
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)
شجرة
الشجرة هي قائمة دليل. لكل إدخال وضع واسم وتجزئة تشير إلى كتلة أو شجرة فرعية.
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 كائن قيمة للقراءة فقط:
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 (وحدة فرعية) |
تُرتب إدخالات الشجرة حسب الاسم، مع التعامل مع أسماء المجلدات كما لو كانت تنتهي بـ/ لأغراض المقارنة.
إيداع
يربط الالتزام شجرةً بسجلها التاريخي وبياناتها الوصفية.
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
إنشاء محتوى الالتزام
$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>
وسم
الوسوم المشروحة كائنات تتضمن بيانات وصفية.
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).
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}، أو SHA-256 للبايتات نفسها. ويختار fromHex() وfromBinary() الخوارزمية بحسب الطول.
تعداد ObjectType
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")
قراءة الكائنات المنفصلة
use Pitmaster\Storage\LooseObjectStore;
$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);
يقوم LooseObjectStore بما يلي:
- يحسب مسار الملف انطلاقًا من التجزئة
- يقرأ الملف ويفك ضغطه باستخدام zlib
- يحلل الترويسة لاستخراج النوع والحجم
- يفوّض المهمة إلى المحلل المناسب (
BlobوTreeوCommitوTag)
كتابة الكائنات المنفصلة
$store->write($object);
خطوات الكتابة:
- التسلسل:
"{type} {size}\0{content}" - استخدام
idالخاص بالكائن، المحسوب عند إنشائه (ويتحقق من التجزئة عند القراءة) - الضغط:
zlib_encode(serialized) - الكتابة إلى
.git/objects/{hash[0..1]}/{rest of hash} - كتابة ذرية (الكتابة إلى ملف مؤقت ثم إعادة تسميته)
ObjectSerializer
يتولى ObjectSerializer ترميز تنسيق الكائنات على السلك وفك ترميزه.
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 مخزن مركّب يبحث أولًا عن الكائنات المنفصلة، ثم عن ملفات الحزم.
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 لكل مستودع عند إنشائه:
$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.