Submodules
Git submodules nemen één repository op in een andere. Een submodule wordt in de parent-tree weergegeven als een gitlink-vermelding (modus 160000) die verwijst naar een specifieke commit in de repository van de submodule. De URL en het pad van de submodule worden opgegeven in het bestand .gitmodules.
Concepten
In de tree van de parent-repository:
100644 blob abc123... README.md
040000 tree def456... src/
160000 commit 789012... lib/external <-- gitlink entry (submodule)
De gitlink-vermelding legt vast welke commit van de submodule moet worden uitgecheckt. De daadwerkelijke repository van de submodule staat in .git/modules/<name>/.
SubmoduleManager
use Pitmaster\Submodule\SubmoduleManager;
$manager = new SubmoduleManager(
$repo->objectDatabase(),
$repo->workDir(),
$repo->gitDir(),
);
Submodules weergeven
$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";
}
}
Submodule-waardeobject
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 parsen
Het bestand .gitmodules bevat de configuratie van de submodule:
[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";
}
De parser verwerkt:
- Sectiekoppen:
[submodule "<name>"] - Sleutel-waardeparen:
path = ...,url = ...,branch = ... - Opmerkingen (regels die beginnen met
#) - Witruimte en inspringing
Vermeldingen zonder zowel path als url worden overgeslagen. Waarden worden niet van aanhalingstekens ontdaan.
Gitlink-vermeldingen
Gitlink-vermeldingen zijn tree-vermeldingen met modus 160000. Ze verwijzen naar een commit (geen tree of blob) in de repository van de submodule.
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
if ($entry->isGitlink()) {
echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
}
}
TreeEntry-detectie
$entry->isGitlink(); // true if mode is '160000'
$entry->mode; // '160000'
Submodules initialiseren
$manager->init();
Voor elke submodule die in .gitmodules is opgegeven, doet dit het volgende:
- Maakt een lege map
.git/modules/<name>/aan - Maakt de map voor het submodulepad aan als deze nog niet bestaat
- Registreert
submodule.<name>.active = trueensubmodule.<name>.urlin.git/config
Het haalt niets op en checkt niets uit, en schrijft het bestand .git van de submodule niet.
Submodules bijwerken
$manager->update($repo->head()->tree);
Voor elke submodule die in de opgegeven tree is vastgezet, kopieert update() de git-map van de bronrepository naar .git/modules/<name>/, vervangt de werkbestanden van de submodule door die van de bron, schrijft het bestand .git dat naar de modulemap verwijst, stelt core.worktree in en zet HEAD losgekoppeld op de vastgezette commit.
Het pad van de submodule URL moet lokaal zijn: absoluut of relatief aan de werkdirectory van de parent (git lost relatieve URL's op aan de hand van de externe URL van de parent). Een URL met een schema (https://...) genereert RuntimeException('Submodule update currently supports only local paths: ...').
Na een update kun je elke submodule als afzonderlijke repository openen:
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
Vastgezette commits controleren
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);
if ($pinnedId !== null) {
echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}
De methode pinnedCommit() doorloopt de tree-hiërarchie om de gitlink-vermelding op het opgegeven pad te vinden en retourneert de hash van de doelcommit.
Submodulestatus
$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";
}
Statusvelden:
name: naam van de submodulepath: pad in de werkdirectoryexpected: commit-hash vastgezet in de parent-tree (null als deze ontbreekt)actual: huidige HEAD van de submodule (null als deze niet is geïnitialiseerd)dirty: true als beide commits bekend zijn en verschillen. Wijzigingen in de werkdirectory van de submodule worden niet gecontroleerd.
Mapstructuur
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
Workflow voor submodules
// 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";