Treci la conținut

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:

Cod
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

PHP
use Pitmaster\Submodule\SubmoduleManager;

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

Listarea submodule-urilor

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

Obiectul valoare al submodule-ului

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)

Analizarea .gitmodules

Fișierul .gitmodules declară configurația submodule-ului:

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

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ările gitlink sunt intrări de arbore cu modul 160000. Ele indică un commit (nu un arbore sau blob) din depozitul submodule-ului.

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

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

Detectarea TreeEntry

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

Inițializarea submodule-urilor

PHP
$manager->init();

Pentru fiecare submodule declarat în .gitmodules, se procedează astfel:

  1. Se creează un director .git/modules/<name>/ gol
  2. Se creează directorul pentru calea submodule-ului, dacă nu există
  3. Se înregistrează submodule.<name>.active = true și submodule.<name>.url în .git/config

Nu preia și nu extrage nimic și nu scrie fișierul .git al submodule-ului.

Actualizarea submodule-urilor

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

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

Verificarea commit-urilor fixate

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

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

Câmpurile de stare:

  • name: numele submodule-ului
  • path: calea din arborele de lucru
  • expected: 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

Cod
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

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