Aller au contenu

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 :

Code
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

PHP
use Pitmaster\Submodule\SubmoduleManager;

$manager = new SubmoduleManager(
    $repo->objectDatabase(),
    $repo->workDir(),
    $repo->gitDir(),
);

Lister les sous-modules

PHP
$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

PHP
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 :

Ini
[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
PHP
$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.

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.

PHP
$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

PHP
$entry->isGitlink();  // true if mode is '160000'
$entry->mode;         // '160000'

Initialiser les sous-modules

PHP
$manager->init();

Pour chaque sous-module déclaré dans .gitmodules :

  1. Crée un répertoire .git/modules/<name>/ vide
  2. Crée le répertoire du chemin du sous-module s’il n’existe pas
  3. Enregistre submodule.<name>.active = true et submodule.<name>.url dans .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

PHP
$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 :

PHP
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');

Vérifier les commits référencés

PHP
$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

PHP
$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-module
  • path : chemin dans l’arbre de travail
  • expected : 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

Code
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

PHP
// 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";