Submodule-uri
Git Submodule-urile încorporează un depozit în altul. În arborele părinte, un submodule este reprezentat printr-o intrare gitlink (modul 160000) care indică un anumit commit din depozitul submodule-ului. URL și calea submodule-ului sunt declarate în fișierul .gitmodules.
Concepte
În arborele depozitului părinte:
100644 blob abc123... README.md
040000 tree def456... src/
160000 commit 789012... lib/external <-- gitlink entry (submodule)
Intrarea gitlink înregistrează commit-ul submodule-ului care trebuie extras. Depozitul submodule-ului propriu-zis se află în .git/modules/<name>/.
SubmoduleManager
use Pitmaster\Submodule\SubmoduleManager;
$manager = new SubmoduleManager(
$repo->objectDatabase(),
$repo->workDir(),
$repo->gitDir(),
);
Listarea submodule-urilor
$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";
}
}
Obiectul valoare al submodule-ului
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)
Analizarea .gitmodules
Fișierul .gitmodules declară configurația submodule-ului:
[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";
}
Analizatorul gestionează:
- Antete de secțiune:
[submodule "<name>"] - Perechi cheie-valoare:
path = ...,url = ...,branch = ... - Comentarii (linii care încep cu
#) - Spații albe și indentare
Intrările fără ambele valori, path și url, sunt omise. Valorile nu sunt eliminate din ghilimele.
Intrări gitlink
Intrările gitlink sunt intrări de arbore cu modul 160000. Ele indică un commit (nu un arbore sau blob) din depozitul submodule-ului.
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
if ($entry->isGitlink()) {
echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
}
}
Detectarea TreeEntry
$entry->isGitlink(); // true if mode is '160000'
$entry->mode; // '160000'
Inițializarea submodule-urilor
$manager->init();
Pentru fiecare submodule declarat în .gitmodules, se procedează astfel:
- Se creează un director
.git/modules/<name>/gol - Se creează directorul pentru calea submodule-ului, dacă nu există
- Se înregistrează
submodule.<name>.active = trueșisubmodule.<name>.urlîn.git/config
Nu preia și nu extrage nimic și nu scrie fișierul .git al submodule-ului.
Actualizarea submodule-urilor
$manager->update($repo->head()->tree);
Pentru fiecare submodule fixat în arborele dat, update() copiază directorul git al depozitului sursă în .git/modules/<name>/, înlocuiește fișierele de lucru ale submodule-ului cu cele din sursă, scrie fișierul .git care indică directorul modulului, setează core.worktree și detașează HEAD la commit-ul fixat.
URL al submodule-ului trebuie să fie o cale locală: absolută sau relativă la arborele de lucru al părintelui (git rezolvă în schimb URL-urile relative în raport cu remote-ul părintelui URL). Un URL cu schemă (https://...) aruncă RuntimeException('Submodule update currently supports only local paths: ...').
După o actualizare, puteți deschide fiecare submodule ca depozit separat:
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
Verificarea commit-urilor fixate
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);
if ($pinnedId !== null) {
echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}
Metoda pinnedCommit() parcurge ierarhia arborelui pentru a găsi intrarea gitlink la calea dată și returnează hash-ul commit-ului țintă.
Starea submodule-ului
$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";
}
Câmpurile de stare:
name: numele submodule-uluipath: calea din arborele de lucruexpected: hash-ul commit-ului fixat în arborele părinte (null dacă lipsește)actual: HEAD-ul curent al submodule-ului (null dacă nu este inițializat)dirty: true când ambele commit-uri sunt cunoscute și diferă. Modificările din arborele de lucru al submodule-ului nu sunt verificate.
Structura directoarelor
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
Fluxul de lucru cu submodule-uri
// 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";