Sous-modules
Les sous-modules Git intègrent un dépôt dans un autre. Dans l’arbre parent, un sous-module est représenté par une entrée gitlink (mode 160000) qui pointe vers un commit précis du dépôt du sous-module. Le URL et le chemin du sous-module sont déclarés dans le fichier .gitmodules.
Concepts
Dans l’arbre du dépôt parent :
100644 blob abc123... README.md
040000 tree def456... src/
160000 commit 789012... lib/external <-- gitlink entry (submodule)
L’entrée gitlink indique quel commit du sous-module doit être extrait. Le dépôt réel du sous-module se trouve dans .git/modules/<name>/.
SubmoduleManager
use Pitmaster\Submodule\SubmoduleManager;
$manager = new SubmoduleManager(
$repo->objectDatabase(),
$repo->workDir(),
$repo->gitDir(),
);
Lister les sous-modules
$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";
}
}
Objet valeur de sous-module
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)
Analyser .gitmodules
Le fichier .gitmodules déclare la configuration des sous-modules :
[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";
}
L’analyseur prend en charge :
- En-têtes de section :
[submodule "<name>"] - Paires clé-valeur :
path = ...,url = ...,branch = ... - Commentaires (lignes commençant par
#) - Espaces et indentation
Les entrées sans path et url sont ignorées. Les valeurs ne sont pas déséchappées.
Entrées gitlink
Les entrées gitlink sont des entrées d’arbre de mode 160000. Elles pointent vers un commit (et non un arbre ou un blob) du dépôt du sous-module.
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
if ($entry->isGitlink()) {
echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
}
}
Détection TreeEntry
$entry->isGitlink(); // true if mode is '160000'
$entry->mode; // '160000'
Initialiser les sous-modules
$manager->init();
Pour chaque sous-module déclaré dans .gitmodules :
- Crée un répertoire
.git/modules/<name>/vide - Crée le répertoire du chemin du sous-module s’il n’existe pas
- Enregistre
submodule.<name>.active = trueetsubmodule.<name>.urldans.git/config
Il ne récupère ni n’extrait quoi que ce soit, et n’écrit pas le fichier .git du sous-module.
Mettre à jour les sous-modules
$manager->update($repo->head()->tree);
Pour chaque sous-module référencé dans l’arbre donné, update() copie le répertoire git du dépôt source dans .git/modules/<name>/, remplace les fichiers de travail du sous-module par ceux de la source, écrit le fichier .git pointant vers le répertoire du module, définit core.worktree et détache HEAD sur le commit référencé.
Le URL du sous-module doit être un chemin local : absolu ou relatif à l’arbre de travail parent (git résout plutôt les URL relatives par rapport au URL distant du parent). Un URL avec un schéma (https://...) provoque RuntimeException('Submodule update currently supports only local paths: ...').
Après une mise à jour, vous pouvez ouvrir chaque sous-module en tant que dépôt distinct :
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
Vérifier les commits référencés
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);
if ($pinnedId !== null) {
echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}
La méthode pinnedCommit() parcourt la hiérarchie de l’arbre pour trouver l’entrée gitlink au chemin donné et renvoie le hash du commit cible.
État des sous-modules
$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";
}
Champs d’état :
name: nom du sous-modulepath: chemin dans l’arbre de travailexpected: hash du commit référencé dans l’arbre parent (null s’il est absent)actual: HEAD actuel du sous-module (null s’il n’est pas initialisé)dirty: true lorsque les deux commits sont connus et différents. Les modifications dans l’arbre de travail du sous-module ne sont pas vérifiées.
Structure des répertoires
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
Flux de travail des sous-modules
// 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";