Submodul
Git submodul menyematkan satu repositori di dalam repositori lain. Submodul direpresentasikan dalam tree induk sebagai entri gitlink (mode 160000) yang menunjuk ke commit tertentu di repositori submodul. URL dan jalur submodul dideklarasikan dalam file .gitmodules.
Konsep
Dalam tree repositori induk:
100644 blob abc123... README.md
040000 tree def456... src/
160000 commit 789012... lib/external <-- gitlink entry (submodule)
Entri gitlink mencatat commit submodul mana yang harus di-checkout. Repositori submodul yang sebenarnya berada di .git/modules/<name>/.
SubmoduleManager
use Pitmaster\Submodule\SubmoduleManager;
$manager = new SubmoduleManager(
$repo->objectDatabase(),
$repo->workDir(),
$repo->gitDir(),
);
Daftar submodul
$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";
}
}
Objek nilai submodul
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)
Mengurai .gitmodules
File .gitmodules mendeklarasikan konfigurasi submodul:
[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";
}
Parser menangani:
- Header bagian:
[submodule "<name>"] - Pasangan kunci-nilai:
path = ...,url = ...,branch = ... - Komentar (baris yang diawali
#) - Spasi dan indentasi
Entri tanpa path dan url akan dilewati. Nilai tidak dihapus tanda kutipnya.
Entri gitlink
Entri gitlink adalah entri tree dengan mode 160000. Entri tersebut menunjuk ke commit (bukan tree atau blob) di repositori submodul.
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
if ($entry->isGitlink()) {
echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
}
}
Deteksi TreeEntry
$entry->isGitlink(); // true if mode is '160000'
$entry->mode; // '160000'
Menginisialisasi submodul
$manager->init();
Untuk setiap submodul yang dideklarasikan di .gitmodules, proses ini:
- Membuat direktori
.git/modules/<name>/yang kosong - Membuat direktori jalur submodul jika belum ada
- Mencatat
submodule.<name>.active = truedansubmodule.<name>.urldi.git/config
Proses ini tidak mengambil atau melakukan checkout apa pun, dan tidak menulis file .git submodul.
Memperbarui submodul
$manager->update($repo->head()->tree);
Untuk setiap submodul yang dipatok dalam tree yang diberikan, update() menyalin direktori git repositori sumber ke .git/modules/<name>/, mengganti file kerja submodul dengan file dari sumber, menulis file .git yang menunjuk ke direktori modul, menetapkan core.worktree, dan melepaskan HEAD pada commit yang dipatok.
URL submodul harus berupa jalur lokal: absolut, atau relatif terhadap tree kerja induk (git menyelesaikan URL relatif berdasarkan remote induk URL). URL dengan skema (https://...) melempar RuntimeException('Submodule update currently supports only local paths: ...').
Setelah pembaruan, Anda dapat membuka setiap submodul sebagai repositori terpisah:
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
Memeriksa commit yang dipatok
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);
if ($pinnedId !== null) {
echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}
Metode pinnedCommit() menelusuri hierarki tree untuk menemukan entri gitlink pada jalur yang diberikan dan mengembalikan hash commit targetnya.
Status submodul
$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";
}
Kolom status:
name: nama submodulpath: jalur di tree kerjaexpected: hash commit yang dipatok di tree induk (null jika tidak ada)actual: HEAD submodul saat ini (null jika belum diinisialisasi)dirty: true jika kedua commit diketahui dan berbeda. Perubahan di dalam tree kerja submodul tidak diperiksa.
Struktur direktori
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
Alur kerja submodul
// 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";