الوحدات الفرعية
تضم وحدات Git الفرعية مستودعًا داخل مستودع آخر. وتمثل الوحدة الفرعية في شجرة المستودع الأب بإدخال gitlink (الوضع 160000) يشير إلى التزام محدد في مستودع الوحدة الفرعية. ويُحدد URL الوحدة الفرعية ومسارها في الملف .gitmodules.
المفاهيم
في شجرة المستودع الأب:
100644 blob abc123... README.md
040000 tree def456... src/
160000 commit 789012... lib/external <-- gitlink entry (submodule)
يسجل إدخال gitlink الالتزام الذي ينبغي سحبه في الوحدة الفرعية. ويقع مستودع الوحدة الفرعية الفعلي في .git/modules/<name>/.
SubmoduleManager
use Pitmaster\Submodule\SubmoduleManager;
$manager = new SubmoduleManager(
$repo->objectDatabase(),
$repo->workDir(),
$repo->gitDir(),
);
سرد الوحدات الفرعية
$submodules = $manager->list();
foreach ($submodules as $sub) {
echo "{$sub->name}: {$sub->url} at {$sub->path}\n";
if ($sub->branch !== null) {
echo " tracks branch: {$sub->branch}\n";
}
}
كائن قيمة الوحدة الفرعية
use Pitmaster\Submodule\Submodule;
$sub->name; // Submodule name (from .gitmodules section header)
$sub->path; // Path in the working tree
$sub->url; // Remote URL
$sub->branch; // Tracked branch (nullable)
تحليل .gitmodules
يعرّف الملف .gitmodules إعدادات الوحدات الفرعية:
[submodule "lib/external"]
path = lib/external
url = https://github.com/vendor/lib.git
branch = main
[submodule "tools/helper"]
path = tools/helper
url = https://github.com/vendor/helper.git
$content = file_get_contents('.gitmodules');
$submodules = SubmoduleManager::parseGitmodules($content);
foreach ($submodules as $sub) {
echo "{$sub->name} -> {$sub->url}\n";
}
يتعامل المحلل مع:
- عناوين الأقسام:
[submodule "<name>"] - أزواج المفتاح والقيمة:
path = ...وurl = ...وbranch = ... - التعليقات (الأسطر التي تبدأ بـ
#) - المسافات البيضاء والمسافات البادئة
تُتجاوز الإدخالات التي لا تحتوي على كل من path وurl. ولا تُزال علامات الاقتباس من القيم.
إدخالات Gitlink
إدخالات Gitlink هي إدخالات شجرة بوضع 160000. وهي تشير إلى التزام (لا إلى شجرة أو كائن ثنائي) في مستودع الوحدة الفرعية.
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
if ($entry->isGitlink()) {
echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
}
}
اكتشاف TreeEntry
$entry->isGitlink(); // true if mode is '160000'
$entry->mode; // '160000'
تهيئة الوحدات الفرعية
$manager->init();
لكل وحدة فرعية معلنة في .gitmodules، تنفذ هذه العملية ما يلي:
- تنشئ دليل
.git/modules/<name>/فارغًا - تنشئ دليل مسار الوحدة الفرعية إن لم يكن موجودًا
- تسجل
submodule.<name>.active = trueوsubmodule.<name>.urlفي.git/config
لا تجلب هذه العملية أي شيء ولا تسحبه، كما أنها لا تكتب ملف .git الخاص بالوحدة الفرعية.
تحديث الوحدات الفرعية
$manager->update($repo->head()->tree);
لكل وحدة فرعية محددة في الشجرة المعطاة، ينسخ update() دليل git الخاص بالمستودع المصدر إلى .git/modules/<name>/، ويستبدل ملفات العمل الخاصة بالوحدة الفرعية بملفات المصدر، ويكتب ملف .git الذي يشير إلى دليل الوحدة، ويضبط core.worktree، ويفصل HEAD عند الالتزام المحدد.
يجب أن يكون URL الخاص بالوحدة الفرعية مسارًا محليًا: مطلقًا أو نسبيًا إلى شجرة عمل الأب (إذ يحل git عناوين URL النسبية نسبةً إلى URL البعيد للأب بدلًا من ذلك). ويطرح URL الذي يتضمن مخططًا (https://...) الخطأ RuntimeException('Submodule update currently supports only local paths: ...').
بعد التحديث، يمكنك فتح كل وحدة فرعية كمستودع منفصل:
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
التحقق من الالتزامات المحددة
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);
if ($pinnedId !== null) {
echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}
تتبع طريقة pinnedCommit() تسلسل الشجرة للعثور على إدخال gitlink عند المسار المعطى، وتعيد تجزئة الالتزام الهدف.
حالة الوحدة الفرعية
$headCommit = $repo->head();
$statuses = $manager->status($headCommit->tree);
foreach ($statuses as $status) {
$expected = $status['expected'] ?? 'not pinned';
$actual = $status['actual'] ?? 'not initialized';
$dirty = $status['dirty'] ? ' (dirty)' : '';
echo "{$status['name']}: expected={$expected}, actual={$actual}{$dirty}\n";
}
حقول الحالة:
name: اسم الوحدة الفرعيةpath: المسار في شجرة العملexpected: تجزئة الالتزام المحدد في شجرة الأب (null إن لم يكن موجودًا)actual: HEAD الحالي للوحدة الفرعية (null إن لم تتم تهيئتها)dirty: true عندما يكون الالتزامان معروفين ومختلفين. لا يجري التحقق من التغييرات داخل شجرة عمل الوحدة الفرعية.
بنية المجلدات
project/
.git/
modules/
lib/
external/ # Submodule's actual git directory (.git/modules/<name>)
HEAD
objects/
refs/
...
.gitmodules # Submodule declarations
lib/
external/
.git # File: "gitdir: ../../.git/modules/lib/external"
src/
README.md
src/
README.md
سير عمل الوحدات الفرعية
// 1. Open the parent repo
$repo = Pitmaster::open('/path/to/project');
// 2. List submodules
$manager = new SubmoduleManager(
$repo->objectDatabase(),
$repo->workDir(),
$repo->gitDir(),
);
$submodules = $manager->list();
// 3. Populate the submodules pinned at HEAD (local source paths only)
$manager->update($repo->head()->tree);
// 4. Check status
$statuses = $manager->status($repo->head()->tree);
// 5. Open a submodule as its own repo
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
$subHead = $subRepo->head();
echo "Submodule HEAD: {$subHead->id->hex}\n";