الكتابة إلى المستودعات
تكتب Pitmaster مستودعات git باستخدام التنسيقات الثنائية نفسها التي يستخدمها الملف الثنائي المعتمد git. تُكتب الكائنات في مخزن الكائنات المنفصلة (مضغوطة باستخدام zlib)، ويُكتب الفهرس بتنسيق v2 (أو v3 عند الحاجة إلى أعلام موسعة للإدخالات؛ ويظل إصدار الفهرس الحالي كما هو)، وتُكتب المراجع كملفات منفصلة. يمكن التحقق من البيانات المكتوبة باستخدام git fsck --strict.
تهيئة مستودع
use Pitmaster\Pitmaster;
$repo = Pitmaster::init('/path/to/new-project');
ينشئ هذا:
.git/objects/(مخزن كائنات فارغ).git/refs/heads/و.git/refs/tags/(مجلدا مراجع فارغان).git/HEADويحتوي علىref: refs/heads/main.git/hooks/و.git/info/excludeو.git/description.git/configبإعدادات افتراضية
مرّر 'sha256' كوسيط ثانٍ لإنشاء مستودع SHA-256: Pitmaster::init($path, 'sha256').
لا يضبط الإعداد الافتراضي core.logallrefupdates، لذلك لا يسجل Pitmaster أي إدخالات reflog في المستودع الذي أنشأه إلى أن تضبط هذا المفتاح على true (#8). تضبطه المستودعات التي أنشأها git init مسبقًا.
تجهيز الملفات
إضافة
$repo->add('src/Feature.php');
$repo->add('src/Feature.php', 'tests/FeatureTest.php', 'README.md');
كل وسيط هو مسار ملف نسبةً إلى جذر شجرة العمل. لا تُوسّع المجلدات وأنماط glob، ويؤدي غياب الملف إلى إطلاق RuntimeException. لكل ملف، تقوم Pitmaster بما يلي:
- تقرأ محتوى الملف من شجرة العمل
- تنشئ كائن
Blobوتحسب تجزئته - تكتب الكائن الثنائي إلى
.git/objects/(مضغوطًا باستخدام zlib) - تحدّث إدخال الفهرس لذلك المسار بالتجزئة الجديدة وبيانات الملف الوصفية
إزالة
$repo->remove('old-file.txt');
$repo->remove('-r', 'src');
$repo->remove('--cached', 'old-file.txt');
يزيل المسارات المتعقبة من الفهرس وشجرة العمل. استخدم --cached للإزالة من الفهرس فقط، أو -r / --recursive للمجلدات المتعقبة.
نقل
$repo->mv('old-name.php', 'new-name.php');
يعيد تسمية الملف في شجرة العمل، ويزيل إدخال الفهرس القديم، ويضيف الإدخال الجديد.
إنشاء الالتزامات
$commitId = $repo->commit('Fix null pointer bug');
echo $commitId->hex;
تتم عملية الالتزام كما يلي:
- إنشاء شجرة من الفهرس: تُنظّم القائمة المسطحة لإدخالات الفهرس في تسلسل هرمي للشجرة (تصبح المجلدات كائنات شجرة، والملفات مراجع إلى كائنات ثنائية). تُكتب كل شجرة في مخزن الكائنات.
- تحديد الأصل: يصبح الالتزام الحالي في HEAD هو الأصل. لا يكون لالتزامات الجذر (أول التزام في مستودع) أي أصول.
- تحديد المؤلف: يُؤخذ من متغيري البيئة
PITMASTER_AUTHOR_*وGIT_AUTHOR_*، ثم الثابتينPITMASTER_AUTHOR_NAME/PITMASTER_AUTHOR_EMAIL، ثم.git/config(user.name/user.email)، ثمPitmaster <pitmaster@localhost>. ويُحدّد اسم المُثبِت بالطريقة نفسها. - كتابة كائن الالتزام: يُسلسل محتوى الالتزام (
tree،parent،author،committer، الرسالة)، ثم تُحسب تجزئته ويُكتب. - تحديث HEAD: يُحدّث مرجع الفرع الذي يشير إليه HEAD إلى تجزئة الالتزام الجديد.
تُنفّذ الخطافات خلال هذه الخطوات (pre-commit، prepare-commit-msg، commit-msg، post-commit) ما لم يُفتح المقبض باستخدام ['hooks' => false]. يُطلق commit() خطأً إذا كانت الرسالة مفقودة، أو لم تتغير الشجرة عن HEAD، أو احتوى الفهرس على مسارات غير مدمجة.
مؤلف مخصص
$author = 'Jane Smith <jane@example.com> 1700000000 +0000';
$commitId = $repo->commit('Add feature', $author);
التحقق باستخدام git fsck
تتحقق تغطية التكامل في Pitmaster من الكائنات المكتوبة بمقارنتها مع git fsck --strict، ويمكنك إجراء التحقق نفسه يدويًا:
cd /path/to/repo
git fsck --strict
إذا كتبت Pitmaster كائنًا غير صالح، فسيبلغ git fsck عن الخطأ فورًا.
إدارة الفروع
إنشاء فرع
// Branch from HEAD
$repo->createBranch('feature/new-thing');
// Branch from a specific commit
$repo->createBranch('hotfix', $repo->resolve('v1.0.0'));
ينشئ ملف مرجع في .git/refs/heads/<name> يحتوي على تجزئة الالتزام المستهدف.
حذف فرع
$repo->deleteBranch('feature/old');
تبديل الفروع
$repo->checkout('feature/login');
يحدّث HEAD ليصبح مرجعًا رمزيًا يشير إلى الفرع، ويعيد ضبط الفهرس ليطابق شجرة طرف الفرع، ويحدّث شجرة العمل.
تؤدي التغييرات المحلية في المسارات المختلفة بين الشجرتين إلى إيقاف عملية التبديل مع RuntimeException. أما التغييرات غير الملتزم بها في جميع الملفات المتعقبة الأخرى، فيُستبدل محتواها بمحتوى الفرع، كما يحتفظ بها git (#2). التزم بها أو خزّنها مؤقتًا قبل التبديل.
التحقق من الدمج
if ($repo->isBranchMerged('feature/done')) {
$repo->deleteBranch('feature/done');
}
إدارة الوسوم
وسم خفيف
$repo->createLightweightTag('v1.0.0'); // At HEAD
$repo->createLightweightTag('v0.9.0', $repo->resolve('HEAD~5'));
الوسم الخفيف هو مجرد مرجع يشير مباشرةً إلى التزام.
وسم مشروح
$tagId = $repo->createTag('v2.0.0', 'Release version 2.0.0');
ينشئ الوسم المشروح كائن وسم (يتضمن واضع الوسم والرسالة والهدف) ومرجعًا يشير إليه. واضع الوسم هو هوية المُثبِت الحالي، ويُحدّد بالطريقة نفسها التي يُحدّد بها مؤلف الالتزام.
واضع وسم مخصص
$tagId = $repo->createTag(
'v2.0.0',
'Release version 2.0.0',
$repo->resolve('HEAD'),
'Jane Smith <jane@example.com> 1700000000 +0000',
);
تحديث المراجع مباشرةً
$repo->updateRef('refs/heads/main', $commitId);
$repo->updateRef('refs/tags/latest', $commitId);
كتابة الكائنات مباشرةً
لحالات الاستخدام المتقدمة، يمكنك كتابة الكائنات دون المرور عبر API عالي المستوى.
كتابة كائن ثنائي
use Pitmaster\Object\Blob;
$blob = Blob::fromContent('Hello, world!');
$id = $repo->writeObject($blob);
echo $id->hex; // SHA-1 of "blob 13\0Hello, world!"
في مستودع SHA-256، مرّر الخوارزمية: Blob::fromContent('Hello, world!', 'sha256').
كتابة شجرة
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;
use Pitmaster\Object\ObjectId;
$entries = [
new TreeEntry('100644', 'README.md', $blobId),
new TreeEntry('40000', 'src', $subtreeId),
];
$tree = Tree::fromEntries($entries);
$id = $repo->writeObject($tree);
تكتب Tree::fromEntries() الإدخالات بالترتيب المحدد وسلاسل الأنماط كما هي. مرّرها بترتيب شجرة git (حسب الاسم، مع مقارنة المجلدات كما لو أن أسمائها تنتهي بـ /)، واكتب أنماط المجلدات بصيغة 40000: يخزّن git أنماط الشجرة دون الصفر البادئ، ويرفض git fsck --strict القيمة 040000.
كتابة التزام
use Pitmaster\Object\Commit;
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;
$content = Commit::buildContent(
tree: $treeId,
parents: [$parentId],
author: 'Name <email> 1700000000 +0000',
committer: 'Name <email> 1700000000 +0000',
message: "Commit message\n",
);
$commitId = ObjectId::compute(ObjectType::Commit, $content);
$commit = Commit::parse($content, $commitId);
$repo->writeObject($commit);
إعادة الضبط
// Soft: move HEAD only
$repo->reset('HEAD~1', 'soft');
// Mixed: move HEAD + reset index (default)
$repo->reset('HEAD~1');
// Hard: move HEAD + reset index + reset working tree
$repo->reset('HEAD~3', 'hard');
الاستعادة
// Restore a file from the index into the worktree
$repo->restore('src/File.php');
// Restore the index version from HEAD (the worktree is left alone)
$repo->restore('src/File.php', staged: true);
// Restore from a specific commit
$repo->restore('src/File.php', 'HEAD~1');
// Restore both index and worktree from a specific commit
$repo->restore('src/File.php', 'HEAD~1', staged: true, worktree: true);
الانتقاء والتراجع
// Apply a commit on the current branch
$newId = $repo->cherryPick('feature~1');
// Undo a commit
$revertId = $repo->revert('HEAD~2');
يقبل كلاهما أي مراجعة يقبلها resolve(). تعمل التجزئات الكاملة، أما المختصرة فلا تعمل.