Ga naar de inhoud

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:

Code
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

PHP
use Pitmaster\Submodule\SubmoduleManager;

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

Submodules weergeven

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";
    }
}

Submodule-waardeobject

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)

.gitmodules parsen

Het bestand .gitmodules bevat de configuratie van de submodule:

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";
}

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 zijn tree-vermeldingen met modus 160000. Ze verwijzen naar een commit (geen tree of blob) in de repository van de submodule.

PHP
$tree = $repo->readObject($treeHash);

foreach ($tree->entries as $entry) {
    if ($entry->isGitlink()) {
        echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
    }
}

TreeEntry-detectie

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

Submodules initialiseren

PHP
$manager->init();

Voor elke submodule die in .gitmodules is opgegeven, doet dit het volgende:

  1. Maakt een lege map .git/modules/<name>/ aan
  2. Maakt de map voor het submodulepad aan als deze nog niet bestaat
  3. Registreert submodule.<name>.active = true en submodule.<name>.url in .git/config

Het haalt niets op en checkt niets uit, en schrijft het bestand .git van de submodule niet.

Submodules bijwerken

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

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

Vastgezette commits controleren

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

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";
}

Statusvelden:

  • name: naam van de submodule
  • path: pad in de werkdirectory
  • expected: 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

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

Workflow voor submodules

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